UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

203 lines (185 loc) 7.66 kB
import {BaseSopOperation} from './_Base'; import {CoreGroup} from '../../../core/geometry/Group'; import {InputCloneMode} from '../../../engine/poly/InputCloneMode'; import {BufferAttribute, Matrix4, Mesh, Triangle, Vector2, Vector3} from 'three'; import {Intersection} from 'three'; import {isBooleanTrue} from '../../../core/BooleanValue'; import {MatDoubleSideTmpSetter} from '../../../core/render/MatDoubleSideTmpSetter'; import {DefaultOperationParams} from '../../../core/operations/_Base'; import {TypeAssert} from '../../poly/Assert'; import {HitPointInfo} from 'three-mesh-bvh'; import {BufferGeometryWithBVH} from '../../../core/geometry/bvh/three-mesh-bvh'; import {ThreeMeshBVHHelper} from '../../../core/geometry/bvh/ThreeMeshBVHHelper'; import {createRaycaster} from '../../../core/RaycastHelper'; import {corePointClassFactory} from '../../../core/geometry/CoreObjectFactory'; import {CoreObjectType} from '../../../core/geometry/ObjectContent'; import {CorePoint} from '../../../core/geometry/entities/point/CorePoint'; export enum RaySopMode { PROJECT_RAY = 'project rays', MIN_DIST = 'minimum distance', } export const RAY_SOP_MODES: RaySopMode[] = [RaySopMode.PROJECT_RAY, RaySopMode.MIN_DIST]; interface RaySopParams extends DefaultOperationParams { mode: number; useNormals: boolean; direction: Vector3; transformPoints: boolean; transferFaceNormals: boolean; transferUVs: boolean; addDistAttribute: boolean; } const DIST_ATTRIB_NAME = 'dist'; const objectWorldMat = new Matrix4(); const objectWorldMatInverse = new Matrix4(); const _points: CorePoint<CoreObjectType>[] = []; const _uv0 = new Vector2(); const _uv1 = new Vector2(); const _uv2 = new Vector2(); const _uv = new Vector2(); export class RaySopOperation extends BaseSopOperation { static override readonly DEFAULT_PARAMS: RaySopParams = { mode: RAY_SOP_MODES.indexOf(RaySopMode.PROJECT_RAY), useNormals: true, direction: new Vector3(0, -1, 0), transformPoints: true, transferFaceNormals: true, transferUVs: false, addDistAttribute: false, }; static override readonly INPUT_CLONED_STATE = [InputCloneMode.FROM_NODE, InputCloneMode.NEVER]; static override type(): Readonly<'ray'> { return 'ray'; } private _matDoubleSideTmpSetter = new MatDoubleSideTmpSetter(); private _raycaster = createRaycaster(); override cook(inputCoreGroups: CoreGroup[], params: RaySopParams) { const coreGroupToRay = inputCoreGroups[0]; const coreGroupToRayOnto = inputCoreGroups[1]; const coreGroup = this._ray(coreGroupToRay, coreGroupToRayOnto, params); return coreGroup; } private _pointPos = new Vector3(); private _pointNormal = new Vector3(); private _hitPointInfo: HitPointInfo = { point: new Vector3(), distance: -1, faceIndex: -1, }; private _triangle = new Triangle(); private _faceNormal = new Vector3(); private _ray(coreGroup: CoreGroup, coreGroupCollision: CoreGroup, params: RaySopParams) { const mode = RAY_SOP_MODES[params.mode]; switch (mode) { case RaySopMode.PROJECT_RAY: { return this._computeWithProjectRay(coreGroup, coreGroupCollision, params); } case RaySopMode.MIN_DIST: { return this._computeWithMinDist(coreGroup, coreGroupCollision, params); } } TypeAssert.unreachable(mode); } private _computeWithProjectRay(coreGroup: CoreGroup, coreGroupCollision: CoreGroup, params: RaySopParams) { this._matDoubleSideTmpSetter.setCoreGroupMaterialDoubleSided(coreGroupCollision); this._addDistAttribute(coreGroup, params); let direction: Vector3, firstIntersect: Intersection; coreGroup.points(_points); for (const point of _points) { point.position(this._pointPos); direction = params.direction; if (isBooleanTrue(params.useNormals)) { point.normal(this._pointNormal); direction = this._pointNormal; } this._raycaster.set(this._pointPos, direction); firstIntersect = this._raycaster.intersectObjects(coreGroupCollision.threejsObjects(), true)[0]; if (firstIntersect) { if (isBooleanTrue(params.transformPoints)) { point.setPosition(firstIntersect.point); } if (isBooleanTrue(params.addDistAttribute)) { const dist = this._pointPos.distanceTo(firstIntersect.point); point.setAttribValue(DIST_ATTRIB_NAME, dist); } if (isBooleanTrue(params.transferFaceNormals) && firstIntersect.face) { point.setNormal(firstIntersect.face.normal); } } } this._matDoubleSideTmpSetter.restoreMaterialSideProperty(coreGroupCollision); return coreGroup; } private _computeWithMinDist(coreGroup: CoreGroup, coreGroupCollision: CoreGroup, params: RaySopParams) { this._addDistAttribute(coreGroup, params); const coreGroupCollisionObject = coreGroupCollision.threejsObjectsWithGeo()[0]; const collisionGeometry = coreGroupCollisionObject.geometry as BufferGeometryWithBVH; const indexArray = collisionGeometry.getIndex()?.array; if (!indexArray) { this.states?.error.set('the collision geo requires an index'); return coreGroup; } // find or create bvh let bvh = collisionGeometry.boundsTree; if (!bvh) { ThreeMeshBVHHelper.assignDefaultBVHIfNone(coreGroupCollisionObject as Mesh); bvh = collisionGeometry.boundsTree; } coreGroupCollisionObject.updateMatrixWorld(true); objectWorldMat.copy(coreGroupCollisionObject.matrixWorld); objectWorldMatInverse.copy(objectWorldMat).invert(); // find closest pt const position = collisionGeometry.getAttribute('position') as BufferAttribute; const uv = collisionGeometry.getAttribute('uv') as BufferAttribute | null; coreGroup.points(_points); const pointsCount = _points.length; for (let i = 0; i < pointsCount; i++) { const point = _points[i]; point.position(this._pointPos); // apply object inverse matrix this._pointPos.applyMatrix4(objectWorldMatInverse); bvh.closestPointToPoint(this._pointPos, this._hitPointInfo); if (isBooleanTrue(params.transformPoints)) { // apply object matrix when setting the position this._hitPointInfo.point.applyMatrix4(objectWorldMat); point.setPosition(this._hitPointInfo.point); } if (isBooleanTrue(params.addDistAttribute)) { point.setAttribValue(DIST_ATTRIB_NAME, this._hitPointInfo.distance); } if (isBooleanTrue(params.transferFaceNormals) || isBooleanTrue(params.transferUVs)) { // TODO: test if applying the object matrix is necessary (probably is) this._triangle.setFromAttributeAndIndices( position, indexArray[3 * this._hitPointInfo.faceIndex], indexArray[3 * this._hitPointInfo.faceIndex + 1], indexArray[3 * this._hitPointInfo.faceIndex + 2] ); if (isBooleanTrue(params.transferFaceNormals)) { this._triangle.getNormal(this._faceNormal); point.setNormal(this._faceNormal); } if (isBooleanTrue(params.transferUVs) && uv) { _uv0.fromBufferAttribute(uv, indexArray[3 * this._hitPointInfo.faceIndex]); _uv1.fromBufferAttribute(uv, indexArray[3 * this._hitPointInfo.faceIndex + 1]); _uv2.fromBufferAttribute(uv, indexArray[3 * this._hitPointInfo.faceIndex + 2]); this._triangle.getInterpolation(this._hitPointInfo.point, _uv0, _uv1, _uv2, _uv); point.setAttribValue('uv', _uv); } } } return coreGroup; } private _addDistAttribute(coreGroup: CoreGroup, params: RaySopParams) { if (isBooleanTrue(params.addDistAttribute) == false) { return; } if (coreGroup.hasPointAttrib(DIST_ATTRIB_NAME) == true) { return; } const allObjects = coreGroup.allObjects(); for (const object of allObjects) { const corePointClass = corePointClassFactory(object); corePointClass.addNumericAttribute(object, DIST_ATTRIB_NAME, 1, -1); } } }