UNPKG

@polygonjs/polygonjs

Version:

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

266 lines (249 loc) 9.33 kB
import {ref} from '@vue/reactivity'; import {PolyScene} from '../../../PolyScene'; import {ActorsManager} from '../../ActorsManager'; import {Object3D, Intersection, Vector2, Vector3, Vector4, PerspectiveCamera, OrthographicCamera} from 'three'; import type {RaycasterUpdateOptions} from '../../events/PointerEventsController'; import {ACTOR_COMPILATION_CONTROLLER_DUMMY_OBJECT} from '../../../../../core/actor/ActorCompilationController'; import {RenderPixelController, coreCursorToUv} from '../../../../../core/render/renderPixel/RenderPixelController'; import {coreGetDefaultCamera} from '../../../../../core/render/renderPixel/CoreGetDefautCamera'; import {pushOnArrayAtEntry} from '../../../../../core/MapUtils'; import { CPUOptions, ObjectOptions, hasCPUOptions, hasGPUOptions, CPUOptionsEqual, CPUOptionsMax, GPUOptionsDepthBufferRequired, } from './Common'; import {arrayCopy} from '../../../../../core/ArrayUtils'; const RAYCAST_UPDATE_OPTIONS: RaycasterUpdateOptions = { pointsThreshold: 0.1, lineThreshold: 0.1, }; const TMP_CPU_OPTIONS: CPUOptions = { traverseChildren: false, pointsThreshold: 0.1, lineThreshold: 0.1, intersectionRef: ref(null), }; function intersectsSort(a: CPUOrGPUIntersection, b: CPUOrGPUIntersection) { return a.distance - b.distance; } const pixelRenderUv = new Vector2(); const pixelRenderTarget = new Vector4(); const raycasterDirNormalised = new Vector3(); const gpuCameraRayAtNearPlane = new Vector3(); const gpuCameraRayAtFarPlane = new Vector3(); const gpuHitPos = new Vector3(); interface GPUIntersection { distance: number; } type CPUOrGPUIntersection = Intersection | GPUIntersection; export class BaseRayObjectIntersectionsController { protected _scene: PolyScene; protected _objects: Object3D[] = []; protected _propertiesListByObject: Map<Object3D, ObjectOptions[]> = new Map(); private _intersectsByObject: WeakMap<Object3D, Intersection[]> = new WeakMap(); private _closestIntersects: Map<Object3D, CPUOrGPUIntersection | undefined> = new Map(); private _objectByClosestIntersect: Map<CPUOrGPUIntersection, Object3D> = new Map(); private _closestIntersectsSorted: CPUOrGPUIntersection[] = []; private _renderPixelController: RenderPixelController = new RenderPixelController(); constructor(protected actorsManager: ActorsManager) { this._scene = actorsManager.scene; } objects(target: Object3D[]) { arrayCopy(this._objects, target); } protected _setIntersectedState(objects: Object3D[], intersectedStateByObject: WeakMap<Object3D, boolean>) { // prepare if (objects.length == 0) { return; } this._closestIntersects.clear(); this._objectByClosestIntersect.clear(); // const pointerEventsController = this._scene.eventsDispatcher.pointerEventsController; const raycaster = pointerEventsController.raycaster().value; pointerEventsController.updateRaycast(RAYCAST_UPDATE_OPTIONS); // prepare gpu options const gpuObjectsPresent = this._gpuObjectsPresent(); const camera = gpuObjectsPresent == true ? (coreGetDefaultCamera(this._scene) as PerspectiveCamera | OrthographicCamera) : null; if (gpuObjectsPresent == true && camera) { const cursor = pointerEventsController.cursor().value; coreCursorToUv(cursor, pixelRenderUv); if (this._gpuDepthBufferReadRequired()) { raycasterDirNormalised.copy(raycaster.ray.direction).normalize(); gpuCameraRayAtNearPlane.set(cursor.x, cursor.y, -1).unproject(camera); gpuCameraRayAtFarPlane.set(cursor.x, cursor.y, 1).unproject(camera); } } // get intersects for (const object of objects) { intersectedStateByObject.set(object, false /*we reset to false here*/); const propertiesList = this._propertiesListByObject.get(object); const intersects = this._intersectsByObject.get(object); if (propertiesList && intersects) { intersects.length = 0; if (hasCPUOptions(propertiesList)) { const cpuOptions = CPUOptionsEqual(propertiesList) ? propertiesList[0].cpu! : CPUOptionsMax(propertiesList, TMP_CPU_OPTIONS); RAYCAST_UPDATE_OPTIONS.pointsThreshold = cpuOptions.pointsThreshold; RAYCAST_UPDATE_OPTIONS.lineThreshold = cpuOptions.lineThreshold; raycaster.intersectObject(object, cpuOptions.traverseChildren, intersects); const closestIntersect = intersects[0]; this._closestIntersects.set(object, closestIntersect); if (closestIntersect) { this._objectByClosestIntersect.set(closestIntersect, object); // console.log({object: object.name, distance: closestIntersect.distance}); } } if (hasGPUOptions(propertiesList)) { const gpuOptions = propertiesList[0].gpu; if (gpuOptions && camera) { const worldPosMaterial = gpuOptions.worldPosMaterial; if (worldPosMaterial != null) { this._renderPixelController.renderColor( this._scene, object, gpuOptions.worldPosMaterial, camera, null, //necessary to have alpha=0 when no object is hit pixelRenderUv, pixelRenderTarget ); } else { this._renderPixelController.renderDepth( this._scene, object, // gpuOptions.worldPosMaterial, camera, null, //necessary to have alpha=0 when no object is hit pixelRenderUv, pixelRenderTarget ); } if (pixelRenderTarget.w > 0 /* pixelRenderTarget.w >= gpuOptions.alphaTest*/) { if (worldPosMaterial) { gpuHitPos.set(pixelRenderTarget.x, pixelRenderTarget.y, pixelRenderTarget.z); } else { gpuHitPos .copy(gpuCameraRayAtNearPlane) .lerp(gpuCameraRayAtFarPlane, pixelRenderTarget.x); } const distance = gpuHitPos.distanceTo(raycaster.ray.origin); const gpuIntersect: GPUIntersection = {distance}; this._closestIntersects.set(object, gpuIntersect); if (gpuIntersect) { this._objectByClosestIntersect.set(gpuIntersect, object); } } else { this._closestIntersects.set(object, undefined); } } } } } // sort closest intersects this._closestIntersectsSorted.length = 0; for (const object of objects) { const closestIntersect = this._closestIntersects.get(object); if (closestIntersect) { this._closestIntersectsSorted.push(closestIntersect); } const propertiesList = this._propertiesListByObject.get(object); if (propertiesList) { for (const properties of propertiesList) { const cpuOptions = properties.cpu; if (cpuOptions) { cpuOptions.intersectionRef.value = (closestIntersect as Intersection) || null; } else { const gpuOptions = properties.gpu; if (gpuOptions) { gpuOptions.distanceRef.value = closestIntersect ? closestIntersect.distance : -1; } } } } } this._closestIntersectsSorted.sort(intersectsSort); // process objects in order let blockingObjectProcessed = false; for (const intersect of this._closestIntersectsSorted) { const object = this._objectByClosestIntersect.get(intersect); if (object) { const propertiesList = this._propertiesListByObject.get(object); if (propertiesList) { let blockObjectsBehind = false; for (const properties of propertiesList) { if (blockingObjectProcessed == false || properties.priority.skipIfObjectsInFront == true) { intersectedStateByObject.set(object, true); } if (properties.priority.blockObjectsBehind == true) { blockObjectsBehind = true; } } blockingObjectProcessed = blockObjectsBehind; } } } // reset this._objectByClosestIntersect.clear(); } // protected _postProcess() { // this._objectByClosestIntersect.clear(); // } private _gpuObjectsPresent(): boolean { const objects = this._objects; for (const object of objects) { const propertiesList = this._propertiesListByObject.get(object); if (propertiesList && hasGPUOptions(propertiesList)) { return true; } } return false; } private _gpuDepthBufferReadRequired(): boolean { const objects = this._objects; for (const object of objects) { const propertiesList = this._propertiesListByObject.get(object); if (propertiesList && GPUOptionsDepthBufferRequired(propertiesList)) { return true; } } return false; } addPropertiesForObject(object: Object3D, properties: ObjectOptions) { if (object == ACTOR_COMPILATION_CONTROLLER_DUMMY_OBJECT) { return; } pushOnArrayAtEntry(this._propertiesListByObject, object, properties); const index = this._objects.indexOf(object); if (index < 0) { this._objects.push(object); this._intersectsByObject.set(object, []); } } removePropertiesForObject(object: Object3D, properties: ObjectOptions) { if (object == ACTOR_COMPILATION_CONTROLLER_DUMMY_OBJECT) { return; } const propertiesForObject = this._propertiesListByObject.get(object); if (propertiesForObject) { const propertyIndex = propertiesForObject.indexOf(properties); propertiesForObject.splice(propertyIndex, 1); if (propertiesForObject.length == 0) { const objectIndex = this._objects.indexOf(object); if (objectIndex >= 0) { this._objects.splice(objectIndex, 1); this._intersectsByObject.delete(object); this._propertiesListByObject.delete(object); } } } } }