@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
235 lines (215 loc) • 7.86 kB
text/typescript
import {Number2, Number3} from '../../../../../types/GlobalTypes';
import {RaycastEventNode, TargetType, TARGET_TYPES} from '../../Raycast';
import {Object3D} from 'three';
import {Intersection} from 'three';
import {NodeContext} from '../../../../poly/NodeContext';
import {BaseObjNodeType} from '../../../obj/_Base';
import {GeoObjNode} from '../../../obj/Geo';
import {TypeAssert} from '../../../../poly/Assert';
import {Plane} from 'three';
import {Vector3} from 'three';
import {ParamType} from '../../../../poly/ParamType';
import {AttribType, ATTRIBUTE_TYPES} from '../../../../../core/geometry/Constant';
import {Vector3Param} from '../../../../params/Vector3';
import {RaycastCPUVelocityController} from './VelocityController';
import {isString} from '../../../../../core/Type';
import {CPUIntersectWith, CPU_INTERSECT_WITH_OPTIONS} from './CpuConstants';
import {isBooleanTrue} from '../../../../../core/BooleanValue';
import {IntersectDataEventNode} from '../../IntersectData';
import {BaseRaycastController} from './BaseRaycastController';
import {resolveIntersectGeometryAttribute} from '../../../../../core/geometry/intersect/CoreIntersect';
import {EventContext} from '../../../../../core/event/EventContextType';
export class RaycastCPUController extends BaseRaycastController {
private _cursorArray: Number2 = [0, 0];
private _resolvedTargets: Object3D[] | undefined;
public readonly velocityController: RaycastCPUVelocityController;
constructor(private _node: RaycastEventNode) {
super();
this.velocityController = new RaycastCPUVelocityController(this._node);
}
updateMouse(eventContext: EventContext<MouseEvent | DragEvent | PointerEvent | TouchEvent>) {
const viewer = eventContext.viewer;
if (!viewer) {
return;
}
const camera = viewer.camera();
if (!camera) {
return;
}
this._cursorHelper.setCursorForCPU(eventContext, this._cursor);
if (isBooleanTrue(this._node.pv.tmouse)) {
this._cursor.toArray(this._cursorArray);
this._node.p.mouse.set(this._cursorArray);
}
// this._updateFromCursor(canvas);
viewer.raycastersController.raycaster0().setFromCamera(this._cursor, camera);
}
processEvent(context: EventContext<MouseEvent>) {
this._prepareRaycaster(context);
const type = CPU_INTERSECT_WITH_OPTIONS[this._node.pv.intersectWith];
switch (type) {
case CPUIntersectWith.GEOMETRY: {
return this._intersectGeometry(context);
}
case CPUIntersectWith.PLANE: {
return this._intersectPlane(context);
}
}
TypeAssert.unreachable(type);
}
private _plane = new Plane();
private _plane_intersect_target = new Vector3();
private _intersectPlane(eventContext: EventContext<MouseEvent>) {
const viewer = eventContext.viewer;
if (!viewer) {
return;
}
this._plane.normal.copy(this._node.pv.planeDirection);
this._plane.constant = this._node.pv.planeOffset;
viewer.raycastersController.raycaster0().ray.intersectPlane(this._plane, this._plane_intersect_target);
this._setPositionParam(this._plane_intersect_target);
this._node.triggerHit(eventContext);
}
private _intersections: Intersection[] = [];
private _intersectGeometry(eventContext: EventContext<MouseEvent>) {
const viewer = eventContext.viewer;
if (!viewer) {
return;
}
if (!this._resolvedTargets) {
this.updateTarget();
}
if (this._resolvedTargets) {
// clear array before
this._intersections.length = 0;
const intersections = viewer.raycastersController
.raycaster0()
.intersectObjects(
this._resolvedTargets,
isBooleanTrue(this._node.pv.traverseChildren),
this._intersections
);
const intersection = intersections[0];
if (intersection) {
this._node.scene().batchUpdates(() => {
if (isBooleanTrue(this._node.pv.tposition)) {
this._setPositionParam(intersection.point);
}
if (isBooleanTrue(this._node.pv.geoAttribute)) {
this._resolveIntersectAttribute(intersection);
}
});
eventContext.value = {intersect: intersection};
this._node.triggerHit(eventContext);
} else {
this._node.triggerMiss(eventContext);
}
}
}
private _resolveIntersectAttribute(intersection: Intersection) {
const attribType = ATTRIBUTE_TYPES[this._node.pv.geoAttributeType];
let attribValue: string | number | null | undefined = IntersectDataEventNode.resolveObjectAttribute(
intersection,
this._node.pv.geoAttributeName
);
if (attribValue == null) {
attribValue = resolveIntersectGeometryAttribute(intersection, this._node.pv.geoAttributeName, attribType);
}
if (attribValue != null) {
switch (attribType) {
case AttribType.NUMERIC: {
this._node.p.geoAttributeValue1.set(attribValue);
return;
}
case AttribType.STRING: {
if (isString(attribValue)) {
this._node.p.geoAttributeValues.set(attribValue);
}
return;
}
}
TypeAssert.unreachable(attribType);
}
}
private _foundPositionTargetParam: Vector3Param | undefined;
private _hitPositionArray: Number3 = [0, 0, 0];
private _setPositionParam(hitPosition: Vector3) {
hitPosition.toArray(this._hitPositionArray);
if (isBooleanTrue(this._node.pv.tpositionTarget)) {
const targetParam = this._node.pv.positionTarget;
if (this._foundPositionTargetParam == null || isBooleanTrue(this._foundPositionTargetParam.disposed())) {
this._foundPositionTargetParam = targetParam.paramWithType(ParamType.VECTOR3);
}
this._foundPositionTargetParam?.set(this._hitPositionArray);
// }
} else {
this._node.p.position.set(this._hitPositionArray);
}
this.velocityController.process(hitPosition);
}
private _prepareRaycaster(eventContext: EventContext<MouseEvent>) {
const viewer = eventContext.viewer;
if (!viewer) {
return;
}
const pointsParam = viewer.raycastersController.raycaster0().params.Points;
if (pointsParam) {
pointsParam.threshold = this._node.pv.pointsThreshold;
}
// let camera = context.camera;
// if (isBooleanTrue(this._node.pv.overrideCamera)) {
// if (isBooleanTrue(this._node.pv.overrideRay)) {
// this._raycaster.ray.origin.copy(this._node.pv.rayOrigin);
// this._raycaster.ray.direction.copy(this._node.pv.rayDirection);
// } else {
// const foundCameraNode = this._node.pv.camera.nodeWithContext(NodeContext.OBJ, this._node.states.error);
// if (foundCameraNode) {
// if ((CAMERA_TYPES as string[]).includes(foundCameraNode.type())) {
// cameraNode = (<unknown>foundCameraNode) as Readonly<BaseCameraObjNodeType>;
// }
// }
// }
// }
// if (cameraNode && !isBooleanTrue(this._node.pv.overrideRay)) {
// cameraNode.prepareRaycaster(this._cursor, this._raycaster);
// }
}
updateTarget() {
const targetType = TARGET_TYPES[this._node.pv.targetType];
switch (targetType) {
case TargetType.NODE: {
return this._updateTargetFromNode();
}
case TargetType.SCENE_GRAPH: {
return this._updateTargetFromSceneGraph();
}
}
TypeAssert.unreachable(targetType);
}
private _updateTargetFromNode() {
const node = this._node.p.targetNode.value.nodeWithContext(NodeContext.OBJ) as BaseObjNodeType;
if (node) {
const found_obj = isBooleanTrue(this._node.pv.traverseChildren)
? node.object
: (node as GeoObjNode).childrenDisplayController.sopGroup();
if (found_obj) {
this._resolvedTargets = [found_obj];
} else {
this._resolvedTargets = undefined;
}
} else {
this._node.states.error.set('node is not an object');
}
}
private _updateTargetFromSceneGraph() {
const objects: Object3D[] = this._node.scene().objectsByMask(this._node.pv.objectMask) as Object3D[];
if (objects.length > 0) {
this._resolvedTargets = objects;
} else {
this._resolvedTargets = undefined;
}
}
static PARAM_CALLBACK_updateTarget(node: RaycastEventNode) {
node.cpuController.updateTarget();
}
}