@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
203 lines (185 loc) • 7.66 kB
text/typescript
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);
}
}
}