UNPKG

polygonjs-engine

Version:

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

324 lines (303 loc) 11.3 kB
import {Number2, Number3} from '../../../../../types/GlobalTypes'; import {EventContext} from '../../../../scene/utils/events/_BaseEventsController'; import {RaycastEventNode, TargetType, TARGET_TYPES} from '../../Raycast'; import {Object3D} from 'three/src/core/Object3D'; import {Vector2} from 'three/src/math/Vector2'; import {Raycaster, Intersection} from 'three/src/core/Raycaster'; import {NodeContext} from '../../../../poly/NodeContext'; import {BaseObjNodeType} from '../../../obj/_Base'; import {Mesh} from 'three/src/objects/Mesh'; import {Points} from 'three/src/objects/Points'; import {BufferGeometry} from 'three/src/core/BufferGeometry'; import {GeoObjNode} from '../../../obj/Geo'; import {TypeAssert} from '../../../../poly/Assert'; import {Plane} from 'three/src/math/Plane'; import {Vector3} from 'three/src/math/Vector3'; import {ParamType} from '../../../../poly/ParamType'; import {AttribType, ATTRIBUTE_TYPES} from '../../../../../core/geometry/Constant'; import {objectTypeFromConstructor, ObjectType} from '../../../../../core/geometry/Constant'; import {CoreGeometry} from '../../../../../core/geometry/Geometry'; import {BufferAttribute} from 'three/src/core/BufferAttribute'; import {Triangle} from 'three/src/math/Triangle'; import {BaseCameraObjNodeType} from '../../../obj/_BaseCamera'; import {Vector3Param} from '../../../../params/Vector3'; import {Poly} from '../../../../Poly'; import {RaycastCPUVelocityController} from './VelocityController'; import {CoreType} from '../../../../../core/Type'; import {CPUIntersectWith, CPU_INTERSECT_WITH_OPTIONS} from './CpuConstants'; export class RaycastCPUController { private _mouse: Vector2 = new Vector2(); private _mouse_array: Number2 = [0, 0]; private _raycaster = new Raycaster(); private _resolved_targets: Object3D[] | undefined; public readonly velocity_controller: RaycastCPUVelocityController; constructor(private _node: RaycastEventNode) { this.velocity_controller = new RaycastCPUVelocityController(this._node); } update_mouse(context: EventContext<MouseEvent>) { const canvas = context.viewer?.canvas(); if (!(canvas && context.cameraNode)) { return; } if (context.event instanceof MouseEvent) { this._mouse.x = (context.event.offsetX / canvas.offsetWidth) * 2 - 1; this._mouse.y = -(context.event.offsetY / canvas.offsetHeight) * 2 + 1; this._mouse.toArray(this._mouse_array); this._node.p.mouse.set(this._mouse_array); } this._raycaster.setFromCamera(this._mouse, context.cameraNode.object); } process_event(context: EventContext<MouseEvent>) { this._prepare_raycaster(context); const type = CPU_INTERSECT_WITH_OPTIONS[this._node.pv.intersectWith]; switch (type) { case CPUIntersectWith.GEOMETRY: { return this._intersect_with_geometry(context); } case CPUIntersectWith.PLANE: { return this._intersect_with_plane(context); } } TypeAssert.unreachable(type); } private _plane = new Plane(); private _plane_intersect_target = new Vector3(); private _intersect_with_plane(context: EventContext<MouseEvent>) { this._plane.normal.copy(this._node.pv.planeDirection); this._plane.constant = this._node.pv.planeOffset; this._raycaster.ray.intersectPlane(this._plane, this._plane_intersect_target); this._set_position_param(this._plane_intersect_target); this._node.trigger_hit(context); } private _intersect_with_geometry(context: EventContext<MouseEvent>) { if (!this._resolved_targets) { this.update_target(); } if (this._resolved_targets) { const intersections = this._raycaster.intersectObjects(this._resolved_targets, true); const intersection = intersections[0]; if (intersection) { this._set_position_param(intersection.point); if (this._node.pv.geoAttribute == true) { this._resolve_geometry_attribute(intersection); } context.value = {intersect: intersection}; this._node.trigger_hit(context); } else { this._node.trigger_miss(context); } } } private _resolve_geometry_attribute(intersection: Intersection) { const attrib_type = ATTRIBUTE_TYPES[this._node.pv.geoAttributeType]; const val = RaycastCPUController.resolve_geometry_attribute( intersection, this._node.pv.geoAttributeName, attrib_type ); if (val != null) { switch (attrib_type) { case AttribType.NUMERIC: { this._node.p.geoAttributeValue1.set(val); return; } case AttribType.STRING: { if (CoreType.isString(val)) { this._node.p.geoAttributeValues.set(val); } return; } } TypeAssert.unreachable(attrib_type); } } static resolve_geometry_attribute(intersection: Intersection, attribute_name: string, attrib_type: AttribType) { const object_type = objectTypeFromConstructor(intersection.object.constructor); switch (object_type) { case ObjectType.MESH: return this.resolve_geometry_attribute_for_mesh(intersection, attribute_name, attrib_type); case ObjectType.POINTS: return this.resolve_geometry_attribute_for_point(intersection, attribute_name, attrib_type); } // TODO: have the raycast cpu controller work with all object types // TypeAssert.unreachable(object_type) } private static _vA = new Vector3(); private static _vB = new Vector3(); private static _vC = new Vector3(); private static _uvA = new Vector2(); private static _uvB = new Vector2(); private static _uvC = new Vector2(); private static _hitUV = new Vector2(); static resolve_geometry_attribute_for_mesh( intersection: Intersection, attribute_name: string, attrib_type: AttribType ) { const geometry = (intersection.object as Mesh).geometry as BufferGeometry; if (geometry) { const attribute = geometry.getAttribute(attribute_name) as BufferAttribute; if (attribute) { switch (attrib_type) { case AttribType.NUMERIC: { const position = geometry.getAttribute('position') as BufferAttribute; if (intersection.face) { this._vA.fromBufferAttribute(position, intersection.face.a); this._vB.fromBufferAttribute(position, intersection.face.b); this._vC.fromBufferAttribute(position, intersection.face.c); this._uvA.fromBufferAttribute(attribute, intersection.face.a); this._uvB.fromBufferAttribute(attribute, intersection.face.b); this._uvC.fromBufferAttribute(attribute, intersection.face.c); intersection.uv = Triangle.getUV( intersection.point, this._vA, this._vB, this._vC, this._uvA, this._uvB, this._uvC, this._hitUV ); return this._hitUV.x; } return; } case AttribType.STRING: { const core_geometry = new CoreGeometry(geometry); const core_point = core_geometry.points()[0]; if (core_point) { return core_point.stringAttribValue(attribute_name); } return; } } TypeAssert.unreachable(attrib_type); } } } static resolve_geometry_attribute_for_point( intersection: Intersection, attribute_name: string, attrib_type: AttribType ) { const geometry = (intersection.object as Points).geometry as BufferGeometry; if (geometry && intersection.index != null) { switch (attrib_type) { case AttribType.NUMERIC: { const attribute = geometry.getAttribute(attribute_name); if (attribute) { return attribute.array[intersection.index]; } return; } case AttribType.STRING: { const core_geometry = new CoreGeometry(geometry); const core_point = core_geometry.points()[intersection.index]; if (core_point) { return core_point.stringAttribValue(attribute_name); } return; } } TypeAssert.unreachable(attrib_type); } } private _found_position_target_param: Vector3Param | undefined; private _hit_position_array: Number3 = [0, 0, 0]; private _set_position_param(hit_position: Vector3) { hit_position.toArray(this._hit_position_array); if (this._node.pv.tpositionTarget) { if (Poly.playerMode()) { this._found_position_target_param = this._found_position_target_param || this._node.p.positionTarget.found_param_with_type(ParamType.VECTOR3); } else { // Do not cache the param in the editor, but fetch it directly from the operator_path. // The reason is that params are very prone to disappear and be re-generated, // Such as spare params created by Gl Builders const target_param = this._node.p.positionTarget; this._found_position_target_param = target_param.found_param_with_type(ParamType.VECTOR3); } if (this._found_position_target_param) { this._found_position_target_param.set(this._hit_position_array); } } else { this._node.p.position.set(this._hit_position_array); } this.velocity_controller.process(hit_position); } private _prepare_raycaster(context: EventContext<MouseEvent>) { const points_param = this._raycaster.params.Points; if (points_param) { points_param.threshold = this._node.pv.pointsThreshold; } let camera_node: Readonly<BaseCameraObjNodeType> | undefined = context.cameraNode; if (this._node.pv.overrideCamera) { if (this._node.pv.overrideRay) { this._raycaster.ray.origin.copy(this._node.pv.rayOrigin); this._raycaster.ray.direction.copy(this._node.pv.rayDirection); } else { const found_camera_node = this._node.p.camera.found_node_with_context(NodeContext.OBJ); if (found_camera_node) { camera_node = (<unknown>found_camera_node) as Readonly<BaseCameraObjNodeType>; } } } if (camera_node && !this._node.pv.overrideRay) { camera_node.prepare_raycaster(this._mouse, this._raycaster); } } update_target() { const targetType = TARGET_TYPES[this._node.pv.targetType]; switch (targetType) { case TargetType.NODE: { return this._update_target_from_node(); } case TargetType.SCENE_GRAPH: { return this._update_target_from_scene_graph(); } } TypeAssert.unreachable(targetType); } private _update_target_from_node() { const node = this._node.p.targetNode.value.ensure_node_context(NodeContext.OBJ) as BaseObjNodeType; if (node) { const found_obj = this._node.pv.traverseChildren ? node.object : (node as GeoObjNode).children_display_controller.sop_group; if (found_obj) { this._resolved_targets = [found_obj]; } else { this._resolved_targets = undefined; } } else { this._node.states.error.set('node is not an object'); } } private _update_target_from_scene_graph() { const objects: Object3D[] = this._node.scene().objectsByMask(this._node.pv.objectMask); if (objects.length > 0) { this._resolved_targets = objects; } else { this._resolved_targets = undefined; } } async update_position_target() { if (this._node.p.positionTarget.isDirty()) { await this._node.p.positionTarget.compute(); } } static PARAM_CALLBACK_update_target(node: RaycastEventNode) { node.cpu_controller.update_target(); } // static PARAM_CALLBACK_update_position_target(node: RaycastEventNode) { // node.cpu_controller.update_position_target(); // } static PARAM_CALLBACK_print_resolve(node: RaycastEventNode) { node.cpu_controller.print_resolve(); } private print_resolve() { this.update_target(); console.log(this._resolved_targets); } }