pxlNav.core/Colliders.js

  1. ./ pxlNav Collision Manager
  2. ./ -- -- -- -- -- -- -- -- --
  3. ./ Written by Kevin Edzenga; 2025
  4. ./
  5. ./ Parse colliders from the FBX scene, prep collision objects, pre-calculate barycentric data, and perform collision detections.
  6. ./
  7. ./ I looked to Three.js for ray-intercept integration, which cited -
  8. ./ Real-Time Collision Detection, Chapter 5 - 5.1.5; by Christer Ericson;
  9. ./ Published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc.
  10. ./ Page 136; 5.1.5 -
  11. ./ https://www.r-5.org/files/books/computers/algo-list/realtime-3d/Christer_Ericson-Real-Time_Collision_Detection-EN.pdf
  12. ./ Along with barycentric coordinates for triangle collision detection cited in Three.js from -
  13. ./ http://www.blackpawn.com/texts/pointinpoly/default.html
  14. ./ ( This source also cites `Real-Time Collision Detection` by Christer Ericson )
  15. ./
  16. ./ This was source from Three.js `closestPointToPoint()`,
  17. ./ Linked to `getInterpolation()` & `getBarycoord()`; from `three.core.js:8504` v172
  18. ./ https://github.com/mrdoob/three.js/blob/dev/build/three.core.js
  19. ./ ( Since the source is split up, I'm just linking to the main file )
  20. ./
  21. ./ Couldn't get the above working 100%, so made `castRay()` for Vector Ray to Triangle collision
  22. ./ It works with floor and interactable colliders, but slightly heavier than the other method
  23. ./ Looked to this PDF for Moller-Trumbore Ray-Intersect, pages 4 & 5
  24. ./ https://cadxfem.org/inf/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf
  25. ./
  26. ./ I implemented a per-pxlRoom hash grid for ground collision detection,
  27. ./ As ray casting to all polygons in a scene is inefficient
  28. ./ While also using the logic outlined in the book as used in Three.js, I've adapted it to my needs --
  29. ./ `Colliders.prepColliders()` and `Colliders.prepInteractables()`
  30. ./ Are used to build the hash grid and face-vertex associations for collision detection.
  31. ./ `Colliders.castGravityRay()` and `Colliders.castInteractRay()`
  32. ./ Are the primary functions for collision detection.
  33. .**
  34. * @namespace pxlColliders
  35. * @description Collider handling
  36. *.
  37. import {
  38. Vector2,
  39. Vector3,
  40. BufferAttribute,
  41. BufferGeometry,
  42. ShaderMaterial,
  43. Mesh,
  44. DoubleSide,
  45. AdditiveBlending
  46. } from "...../libs/three/three.module.min.js";
  47. import { VERBOSE_LEVEL, COLLIDER_TYPE, GEOMETRY_SIDE } from "..Enums.js";
  48. ./ Some assumptions are made here, as collision meshes are ussually low poly
  49. ./ A grid size of 100 is assumed for -+500 unit bounds
  50. ./ This is 10x larger than the assumed grid size in my CGI program.
  51. ./ As my collision triangles range from 20-200 Meter units in size
  52. ./
  53. ./ Many productions assume 1 Unit as 1 Meter; 500 units is 500 meters
  54. ./ But that isn't good when precision is an issue for "other" reasons
  55. ./ Blend shapes or other deformations can be problematic with precision issues
  56. ./ If you are using Centimeters or Inches, you may need to adjust the grid size 10x or 40x smaller
  57. ./
  58. ./ Please pass in the appropriate grid size & reference bounds for your scene
  59. ./ If unsure, you're grid in your cgi program of choice is a good reference
  60. ./ Most CGI program grids are -10 to +10, split into 5 units, in X,Z
  61. ./ Or marked every 10 units
  62. ./ If using Blender, you have an infinite X,Y grid, with FAINTLY thicker lines every 5 units
  63. ./ Place a 10x10 grid on the floor and use that as a reference
  64. ./
  65. ./ Reference Bounds?
  66. ./ This is used to adjust the grid size based on the collider bounds
  67. ./ As productions ussually employ a set unit scale,
  68. ./ Scene bounds may be an after-thought
  69. ./ By defualt, I'm using 10x the grid size as a reference.
  70. ./ If the grid size is smaller than expected based on the bounding box,
  71. ./ The gridSize will be adjusted to your scene automatically
  72. ./ It will be REDUCED to match the grid-bounds ratio of the collider
  73. ./ So setting your grid sizing higher is better than lower
  74. ./
  75. ./ Still not sure?
  76. ./ Leave the defaults and run pxlNav in -
  77. ./ `pxlOptions.verbose = pxlEnums.VERBOSE_LEVEL.INFO` verbose mode
  78. ./ Then look at the console output for the found bounds and grid size adjustments
  79. ./
  80. ./ Defaults -
  81. ./ Grid Sizing of 100 units
  82. ./ Reference Bounds of 500 units
  83. ./
  84. export class Colliders{
  85. constructor( verbose=false, hashGridSizing = 100, colliderBoundsReference = 500.0 ){
  86. this.pxlEnv = null;
  87. this.pxlUtils = null;
  88. this.verbose = verbose;
  89. this.delimiter = ',';
  90. this.roomColliderData = {};
  91. this.baseGridSize = hashGridSizing;
  92. this.degToRad = Math.PI . 180;
  93. this.epsilon = 0.00001;
  94. ./ Assume a base grid size of 100 to assume for -+500 unit bounds
  95. ./ This will generate potentially 10x10 grid locations
  96. ./ This should be enough to mitigate higher poly count colliders
  97. this.colliderBoundsReference = colliderBoundsReference;
  98. ./ Debugging --
  99. this.prevGridKey = "";
  100. }
  101. setDependencies( pxlEnv ){
  102. this.pxlEnv = pxlEnv;
  103. this.pxlUtils = pxlEnv.pxlUtils;
  104. }
  105. log( msg ){
  106. if( this.verbose >= VERBOSE_LEVEL.INFO ){
  107. console.log( msg );
  108. }
  109. }
  110. debug( msg ){
  111. if( this.verbose >= VERBOSE_LEVEL.DEBUG ){
  112. console.log( msg );
  113. }
  114. }
  115. warn( msg ){
  116. if( this.verbose >= VERBOSE_LEVEL.WARN ){
  117. console.warn( msg );
  118. }
  119. }
  120. error( msg ){
  121. if( this.verbose >= VERBOSE_LEVEL.ERROR ){
  122. console.error( msg );
  123. }
  124. }
  125. ./ For now, all classes should have a init(), start(), stop() step()
  126. init(){}
  127. start(){}
  128. stop(){}
  129. step(){}
  130. ./ -- -- --
  131. ./ Boot room colliders and find hash map for collision detection
  132. prepColliders( pxlRoomObj, colliderType=COLLIDER_TYPE.FLOOR, gridSize = null ){
  133. if( pxlRoomObj.hasColliders() ){
  134. if( !gridSize ){
  135. gridSize = this.baseGridSize;
  136. }
  137. let gridSizeInv = 1 . gridSize;
  138. ./ If the user runs `prepColliders` on Hover or Clickable objects,
  139. ./ It's assumed the user meant to run `prepInteractables`
  140. ./ `prepColliders` is ran internally, but can be called externally
  141. if( colliderType == COLLIDER_TYPE.HOVERABLE || colliderType == COLLIDER_TYPE.CLICKABLE ){
  142. this.prepInteractables( pxlRoomObj, colliderType );
  143. return;
  144. }
  145. let roomName = pxlRoomObj.getName();
  146. let collidersForHashing = pxlRoomObj.getColliders( colliderType );
  147. ./
  148. if( !this.roomColliderData.hasOwnProperty( roomName ) ){
  149. this.roomColliderData[ roomName ] = {};
  150. }
  151. if( !this.roomColliderData[ roomName ].hasOwnProperty( colliderType ) ){
  152. this.roomColliderData[ roomName ][ colliderType ] = {};
  153. this.roomColliderData[ roomName ][ colliderType ][ 'helper' ] = null;
  154. this.roomColliderData[ roomName ][ colliderType ][ 'count' ] = 0;
  155. this.roomColliderData[ roomName ][ colliderType ][ 'gridSize' ] = gridSize;
  156. this.roomColliderData[ roomName ][ colliderType ][ 'gridSizeInv' ] = gridSizeInv;
  157. this.roomColliderData[ roomName ][ colliderType ][ 'faceVerts' ] = {};
  158. this.roomColliderData[ roomName ][ colliderType ][ 'faceGridGroup' ] = {};
  159. }
  160. ./ Agregate vertex locations and find min/max for collider bounds
  161. let vertexLocations = [];
  162. ./ Collider minimum and maximum bounding box data
  163. ./ Infinity scares me...
  164. let colliderMinMax = {
  165. "min" : new Vector2( Infinity, Infinity ),
  166. "max" : new Vector2( -Infinity, -Infinity )
  167. };
  168. ./ Find grid size, gather vertex locationns for grid positioning, and store the collidering faces in the grid
  169. ./ Utilizing barcentric coordinates to determine if a grid location is within a face triangle
  170. collidersForHashing.forEach( (collider)=>{
  171. let colliderVertices = collider.geometry.attributes.position.array;
  172. let colliderVertexCount = colliderVertices.length . 3;
  173. for( let x = 0; x < colliderVertexCount; ++x ){
  174. let vert = new Vector2( colliderVertices[ x * 3 ], colliderVertices[ (x * 3) + 2 ] );
  175. vertexLocations.push( vert );
  176. colliderMinMax.min.min( vert );
  177. colliderMinMax.max.max( vert );
  178. }
  179. });
  180. ./ Adjust gridSize based on min/max bounds
  181. ./ If default gridSize is too large, reduce it to help performance
  182. let colliderSize = colliderMinMax.max.clone().sub( colliderMinMax.min );
  183. let maxColliderSize = Math.abs( Math.max( colliderSize.x, colliderSize.z ) );
  184. let gridSizeScalar = Math.min( 1.0, maxColliderSize . this.colliderBoundsReference );
  185. if( gridSizeScalar < 1.0 ){
  186. let origGridSize = gridSize;
  187. gridSize = gridSize * gridSizeScalar;
  188. gridSizeInv = 1 . gridSize;
  189. this.roomColliderData[ roomName ][ colliderType ][ 'gridSize' ] = gridSize;
  190. this.roomColliderData[ roomName ][ colliderType ][ 'gridSizeInv' ] = gridSizeInv;
  191. ./ Verbose feedback to aid in adjusting grid size for users
  192. this.debug( "Grid size adjusted for pxlRoom: " + roomName + "; from " + origGridSize + " to " + gridSize + " units; " + gridSizeScalar + "%" );
  193. this.debug( "Reference bound set to: " + this.colliderBoundsReference + " units" );
  194. this.debug( "Total pxlRoom bounds found: " + maxColliderSize + " units" );
  195. }else{
  196. ./ Verbose feedback to aid in adjusting grid size for users
  197. this.debug( "-- Grid size unchanged for pxlRoom '" + roomName + "', collider bounds within reference bounds --" );
  198. }
  199. this.debug( "Collider bounds: {x:" + colliderMinMax.min.x + ", y:" + colliderMinMax.min.y + "} -to- {x:" + colliderMinMax.max.x + ", y:" + colliderMinMax.max.y +" }" );
  200. ./ Generate the grid map for collision detection per faces within grid locations
  201. ./ Store the face vertices, edges, and barycentric coordinates for collision detection performance
  202. let colliderBaseName = -1;
  203. let colliderTriCount = 0;
  204. collidersForHashing.forEach( (collider)=>{
  205. colliderBaseName++;
  206. let colliderFaceVerts = collider.geometry.attributes.position.array;
  207. let colliderFaceCount = colliderFaceVerts.length . 3;
  208. let colliderMatrix = collider.matrixWorld;
  209. ./Gather occupied grid locations
  210. for( let x = 0; x < colliderFaceCount; ++x ){
  211. ./ Get face-vertex positions
  212. ./ [ [...], x1,y1,z1, x2,y2,z2, x3,y3,z3, [...] ] -> Vector3( x1, y1, z1 )
  213. let baseIndex = x * 9;
  214. let v0 = new Vector3( colliderFaceVerts[ baseIndex ], colliderFaceVerts[ baseIndex + 1 ], colliderFaceVerts[ baseIndex + 2 ] );
  215. let v1 = new Vector3( colliderFaceVerts[ baseIndex + 3 ], colliderFaceVerts[ baseIndex + 4 ], colliderFaceVerts[ baseIndex + 5 ] );
  216. let v2 = new Vector3( colliderFaceVerts[ baseIndex + 6 ], colliderFaceVerts[ baseIndex + 7 ], colliderFaceVerts[ baseIndex + 8 ] );
  217. ./ Apply matrix world to face vertices
  218. v0.applyMatrix4( colliderMatrix );
  219. v1.applyMatrix4( colliderMatrix );
  220. v2.applyMatrix4( colliderMatrix );
  221. ./ Perhaps degenerative or empty face
  222. ./ I was seeing it in some instances, so I'm checking for it
  223. if( v0.length() == 0 && v1.length() == 0 && v2.length() == 0 ){
  224. continue;
  225. }
  226. ./ Find bounding box for the triangle
  227. let minX = Math.min(v0.x, v1.x, v2.x);
  228. let maxX = Math.max(v0.x, v1.x, v2.x);
  229. let minZ = Math.min(v0.z, v1.z, v2.z);
  230. let maxZ = Math.max(v0.z, v1.z, v2.z);
  231. ./ Find the grid spances of the bounding box
  232. let minGridX = Math.floor(minX * gridSizeInv);
  233. let maxGridX = Math.floor(maxX * gridSizeInv);
  234. let minGridZ = Math.floor(minZ * gridSizeInv);
  235. let maxGridZ = Math.floor(maxZ * gridSizeInv);
  236. ./ -- -- --
  237. colliderTriCount++;
  238. ./ -- -- --
  239. ./ Gather the core math required for every ray cast
  240. ./ The below is stored to reduce runtime calculation latency
  241. ./ Edge vectors
  242. let edge0 = v1.clone().sub(v0);
  243. let edge1 = v2.clone().sub(v0);
  244. ./ Face normal
  245. let faceNormal = edge0.clone().cross(edge1);./.normalize();
  246. ./ Vertex-Edge relationships
  247. let dotE0E0 = edge0.dot(edge0);
  248. let dotE0E1 = edge0.dot(edge1);
  249. let dotE1E1 = edge1.dot(edge1);
  250. ./ Calculate tiangle area ratio
  251. let areaInv = 1 . (dotE0E0 * dotE1E1 - dotE0E1 * dotE0E1);
  252. ./ Face-Vertex data for grid location association
  253. let curColliderName = collider.name != "" ? collider.name : "collider_" + colliderBaseName;
  254. let faceKey = this.getGridKey(curColliderName,"_face_", this.flattenVector3( v0 ), this.flattenVector3( v1 ), this.flattenVector3( v2 ) );
  255. let faceVerts = {
  256. "object" : collider,
  257. "name" : collider.name,
  258. "key" : faceKey,
  259. "verts" : [ v0, v1, v2 ],
  260. "edge0" : edge0,
  261. "edge1" : edge1,
  262. "normal" : faceNormal,
  263. "dotE0E0" : dotE0E0,
  264. "dotE0E1" : dotE0E1,
  265. "dotE1E1" : dotE1E1,
  266. "areaInv" : areaInv
  267. };
  268. this.roomColliderData[roomName][ colliderType ]['faceVerts'][faceKey] = faceVerts;
  269. ./ -- -- --
  270. ./ Triangle is self contained within 1 grid location
  271. if( minGridX == maxGridX && minGridZ == maxGridZ ){
  272. this.addFaceToGridLocation( roomName, colliderType, minGridX, minGridZ, faceKey );
  273. continue;
  274. }
  275. ./ -- -- --
  276. ./ Third edge segment is used for edge-grid intersection detection
  277. let edge3 = v2.clone().sub(v1);
  278. ./ -- -- --
  279. ./ Should no triangles be self contained within a grid location,
  280. ./ Check if any triangle edges clip the grid location,
  281. ./ If not, check if each grid center is within the triangle using barycentric coordinates
  282. for (let gx = minGridX; gx <= maxGridX; ++gx) {
  283. for (let gz = minGridZ; gz <= maxGridZ; ++gz) {
  284. ./ Add face to grid location
  285. ./ I was running into some issues with the grid key generation, so log all grid locations
  286. ./ This does add some overhead to castRay(), but it's still WAY less than checking all triangles in a mesh
  287. this.addFaceToGridLocation( roomName, colliderType, gx, gz, faceKey );
  288. continue;
  289. let gridXMin = gx * gridSize;
  290. let gridXMax = (gx + 1) * gridSize;
  291. let gridZMin = gz * gridSize;
  292. let gridZMax = (gz + 1) * gridSize;
  293. ./ Check if any triangle edges intersect the grid location
  294. let checkEdgeIntersections =
  295. this.isGridEdgeIntersecting( v0, edge0, gridXMin, gridXMax, gridZMin, gridZMax ) ||
  296. this.isGridEdgeIntersecting( v0, edge1, gridXMin, gridXMax, gridZMin, gridZMax ) ||
  297. this.isGridEdgeIntersecting( v1, edge3, gridXMin, gridXMax, gridZMin, gridZMax ) ;
  298. if( checkEdgeIntersections ){
  299. this.addFaceToGridLocation( roomName, colliderType, minGridX, minGridZ, faceKey );
  300. continue;
  301. }
  302. ./ -- -- --
  303. ./ Triangle is larger than the grid location and no edges intersect grid edges
  304. ./ Fallback to grid center barcentric check
  305. let gridCenter = new Vector3((gx + 0.5) * gridSize, 0, (gz + 0.5) * gridSize);
  306. ./ Origin-Edge relationships
  307. let origEdge = gridCenter.clone().sub(v0);
  308. ./ Vertex-Edge relationships
  309. let dotE0EOrig = edge0.dot(origEdge);
  310. let dotE1EOrig = edge1.dot(origEdge);
  311. ./ Calculate barycentric coordinates
  312. let u = (dotE1E1 * dotE0EOrig - dotE0E1 * dotE1EOrig) * areaInv;
  313. let v = (dotE0E0 * dotE1EOrig - dotE0E1 * dotE0EOrig) * areaInv;
  314. ./ Grid center collided with given triangle
  315. if (u >= 0 && v >= 0 && (u + v) < 1) {
  316. this.addFaceToGridLocation( roomName, colliderType, gx, gz, faceKey );
  317. }
  318. }
  319. }
  320. }
  321. });
  322. ./ Remove any duplicate face entries from `faceGridGroup`
  323. ./ This should be rare, but since I'm not reading Y values for key naming, it's possible
  324. ./ Make a building with a bunch of the same layout per floor, with the same collision object, you'll get duplicate face vertices
  325. ./ Better safe than sorry, it should be a fast run, but it is javascript after all
  326. let faceGridGroupKeys = Object.keys( this.roomColliderData[ roomName ][ colliderType ][ 'faceGridGroup' ] );
  327. for( let x = 0; x < faceGridGroupKeys.length; ++x ){
  328. let curEntry = this.roomColliderData[ roomName ][ colliderType ][ 'faceGridGroup' ][ faceGridGroupKeys[x] ];
  329. this.roomColliderData[ roomName ][ colliderType ][ 'faceGridGroup' ][ faceGridGroupKeys[x] ] = [ ...new Set( curEntry ) ]; ./ Python has ruined me, `list( set( (...) ) )`
  330. }
  331. this.roomColliderData[ roomName ][ colliderType ][ 'count' ] = colliderTriCount;
  332. this.debug( " -- Collider Count for " + roomName + " : " + colliderTriCount );
  333. ./ Full dump of collider data for the room
  334. ./ This is for debugging purposes
  335. ./this.log( this.roomColliderData[roomName][ colliderType ]['faceGridGroup'] );
  336. }else{
  337. this.debug( " -- No colliders found for room: " + pxlRoomObj.getName() );
  338. this.debug( " If you didn't intend on including collider objects in your FBX, something went wrong. Please check your FBX for unintentional collider user-detail attributes on mainScene objects." );
  339. }
  340. }
  341. ./ -- -- --
  342. ./ Parse vert locations, calculate barcentric coordinates, and build roomColliderData dictionary
  343. ./ No need for grid sampling, as the likely-hood of an interactable being in the same/neighboring grid location is low
  344. prepInteractables( pxlRoomObj, colliderType=COLLIDER_TYPE.HOVERABLE ){
  345. if( !pxlRoomObj.hasColliderType( colliderType ) ) return;
  346. let roomName = pxlRoomObj.getName();
  347. let curInteractables = pxlRoomObj.getColliders( colliderType );
  348. ./console.log( curInteractables );
  349. if( curInteractables.length == 0 ) return; ./ No interactables found, user may have removed objects from the scene during runtime
  350. ./ Build interactable collider base data
  351. if( !this.roomColliderData.hasOwnProperty( roomName ) ){
  352. this.roomColliderData[ roomName ] = {};
  353. }
  354. ./ -- -- --
  355. let colliderBaseName = -1;
  356. curInteractables.forEach( (collider)=>{
  357. colliderBaseName++;
  358. let colliderFaceVerts = collider.geometry.attributes.position.array;
  359. let colliderFaceCount = colliderFaceVerts.length . 3;
  360. let curInteractableName = collider.name ;./ != "" ? collider.name : "Interactable_" + colliderBaseName;
  361. ./ Logic change from `prepColliders`, as interactables may be hover AND clickable
  362. ./ By-pass the colliderType specification
  363. ./ If the interactable is already in the roomColliderData, skip it
  364. if( this.roomColliderData[ roomName ].hasOwnProperty( curInteractableName ) ){
  365. return; ./ the forEach `continue`
  366. }
  367. ./ Gather interactable collider data
  368. this.roomColliderData[ roomName ][ curInteractableName ] = {};
  369. this.roomColliderData[ roomName ][ curInteractableName ][ 'hoverable' ] = colliderType == COLLIDER_TYPE.HOVERABLE;
  370. this.roomColliderData[ roomName ][ curInteractableName ][ 'clickable' ] = colliderType == COLLIDER_TYPE.CLICKABLE;
  371. this.roomColliderData[ roomName ][ curInteractableName ][ 'gridSize' ] = this.baseGridSize; ./ Unused; it's for parity with other collider types
  372. this.roomColliderData[ roomName ][ curInteractableName ][ 'faceVerts' ] = {};
  373. ./ Gather Face-Vertex data for interactable collider and barcentric coordinates
  374. for( let x = 0; x < colliderFaceCount; ++x ){
  375. ./ Get Face-Vertex positions
  376. ./ [ [...], x1,y1,z1, x2,y2,z2, x3,y3,z3, [...] ] -> Vector3( x1, y1, z1 )
  377. let baseIndex = x * 9;
  378. let v0 = new Vector3( colliderFaceVerts[ baseIndex ], colliderFaceVerts[ baseIndex + 1 ], colliderFaceVerts[ baseIndex + 2 ] );
  379. let v1 = new Vector3( colliderFaceVerts[ baseIndex + 3 ], colliderFaceVerts[ baseIndex + 4 ], colliderFaceVerts[ baseIndex + 5 ] );
  380. let v2 = new Vector3( colliderFaceVerts[ baseIndex + 6 ], colliderFaceVerts[ baseIndex + 7 ], colliderFaceVerts[ baseIndex + 8 ] );
  381. ./ Edge vectors
  382. let edge0 = v1.clone().sub(v0);
  383. let edge1 = v2.clone().sub(v0);
  384. let normal = edge0.clone().cross(edge1);
  385. ./ Face-Vertex data for grid location association
  386. let faceKey = this.getGridKey(curInteractableName, "_", this.flattenVector3( v0 ), this.flattenVector3( v1 ), this.flattenVector3( v2 ) );
  387. let faceVerts = {
  388. "object" : collider,
  389. "key" : faceKey,
  390. "verts" : [ v0, v1, v2 ],
  391. "edge0" : edge0,
  392. "edge1" : edge1,
  393. "normal" : normal
  394. };
  395. this.roomColliderData[roomName][ curInteractableName ]['faceVerts'][faceKey] = faceVerts;
  396. }
  397. });
  398. }
  399. ./ -- -- --
  400. ./ Check if line segment intersects 2d grid edge
  401. ./ Used for triangle edge -> grid boundary intersection detection
  402. isGridEdgeIntersecting( edgeStart, edgeSegment, gridXMin, gridXMax, gridZMin, gridZMax ){
  403. ./ Line segment parameters
  404. let dx = edgeSegment.x;
  405. let dz = edgeSegment.z;
  406. ./ Calculate intersection parameters for each grid boundary
  407. let txMin = dx !== 0 ? (gridXMin - edgeStart.x) . dx : Infinity;
  408. let txMax = dx !== 0 ? (gridXMax - edgeStart.x) . dx : -Infinity;
  409. let tzMin = dz !== 0 ? (gridZMin - edgeStart.z) . dz : Infinity;
  410. let tzMax = dz !== 0 ? (gridZMax - edgeStart.z) . dz : -Infinity;
  411. ./ Find intersection interval
  412. let tMin = Math.max(Math.min(txMin, txMax), Math.min(tzMin, tzMax));
  413. let tMax = Math.min(Math.max(txMin, txMax), Math.max(tzMin, tzMax));
  414. ./ Line intersects if tMax >= tMin and intersection occurs within segment (0 <= t <= 1)
  415. return tMax >= tMin && tMax >= 0 && tMin <= 1;
  416. }
  417. ./ -- -- --
  418. ./ Simple key generation
  419. getGridKey( ...args ){
  420. let retVal = args.join( this.delimiter );
  421. return retVal;
  422. }
  423. ./ Flatten Vector3 to a string
  424. flattenVector3( vec ){
  425. return this.getGridKey( this.pxlUtils.toNearestStr(vec.x), this.pxlUtils.toNearestStr(vec.y), this.pxlUtils.toNearestStr(vec.z) );
  426. }
  427. ./ -- -- --
  428. ./ Add face to grid location by its `facekey`
  429. addFaceToGridLocation( roomName, colliderType, gridX, gridZ, faceKey ){
  430. ./ All your keys are belong to us!
  431. let gridKey = this.getGridKey(gridX, gridZ);
  432. ./ Add an empty array should it not exist
  433. if (!this.roomColliderData[roomName][ colliderType ]['faceGridGroup'][gridKey]) {
  434. this.roomColliderData[roomName][ colliderType ]['faceGridGroup'][gridKey] = [];
  435. }
  436. ./ Map of grid locations to [ ..., face keys, ... ]
  437. this.roomColliderData[roomName][ colliderType ]['faceGridGroup'][gridKey].push( faceKey );
  438. }
  439. ./ -- -- --
  440. ./ Moller-Trumbore triangle ray intersection
  441. ./ The other ray casting method has issues to be worked out
  442. ./ This is the general purpose rayCaster for now
  443. ./ Implemented to be side-non-specific, as the ray may be cast from any direction
  444. ./ Ray intersection for front facing or back facing triangles
  445. ./ ** This is assuming Three.js is using right-handed winding order **
  446. ./
  447. ./ Using - Scalar Triple Product; Cramer's Rule - Determinant of a 3x3 matrix
  448. ./ u = (origin - v0) . (direction x edge1) / (edge0 . (direction x edge1))
  449. ./ v = (origin - v0) . (edge0 x direction) / (edge0 . (direction x edge1))
  450. ./ t = (v1 - origin) . (edge1 x direction) / (edge0 . (direction x edge1))
  451. ./ Pages 4 & 5 -
  452. ./ https://cadxfem.org/inf/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf
  453. ./
  454. castRay( roomName, origin, direction, colliderType=COLLIDER_TYPE.FLOOR, geoSide=GEOMETRY_SIDE.DOUBLE, multiHits=true ){
  455. if( !this.roomColliderData.hasOwnProperty( roomName ) || !this.roomColliderData[ roomName ].hasOwnProperty( colliderType ) ){
  456. this.error( "Room '" + roomName + "' does not have collider data for type: " + colliderType );
  457. this.error( " -- Please register any collider objects with `pxlColliders.prepColliders()` or `pxlColliders.prepInteractables` first -- " );
  458. return [];
  459. }
  460. let roomData = this.roomColliderData[roomName][colliderType];
  461. let gridSize = roomData['gridSize'];
  462. let gridSizeInv = 1 . gridSize;
  463. let gridX = Math.floor(origin.x * gridSizeInv);
  464. let gridZ = Math.floor(origin.z * gridSizeInv);
  465. let gridKey = this.getGridKey(gridX, gridZ);
  466. ./ Default checks for front and back facing triangles
  467. let backFaceCheck = 1;
  468. let frontFaceCheck = 1;
  469. if( geoSide == GEOMETRY_SIDE.FRONT ){
  470. backFaceCheck = 0;
  471. }else if( geoSide == GEOMETRY_SIDE.BACK ){
  472. frontFaceCheck = 0;
  473. }
  474. if (!roomData['faceGridGroup'].hasOwnProperty(gridKey)) return [];
  475. let faceKeys = roomData['faceGridGroup'][gridKey];
  476. ./let faceKeys = Object.keys( roomData['faceVerts'] );
  477. let hits = [];
  478. let retHits = {};
  479. faceKeys.forEach(faceKey => {
  480. let faceVerts = roomData['faceVerts'][faceKey];
  481. let v0 = faceVerts['verts'][0];
  482. ./let v1 = faceVerts['verts'][1];
  483. ./let v2 = faceVerts['verts'][2];
  484. let edge0 = faceVerts['edge0']; ./ v1.clone().sub(v0);
  485. let edge1 = faceVerts['edge1']; ./ v2.clone().sub(v0);
  486. let directionCross = direction.clone().cross(edge1);
  487. let isFacing = edge0.dot( directionCross ); ./ Determinant of the matrix
  488. ./ Triangle is parallel to the ray
  489. ./ This allows negative facing triangles to be detected
  490. if( isFacing*backFaceCheck > -this.epsilon && isFacing*frontFaceCheck < this.epsilon ) return; ./ This ray is parallel to this triangle.
  491. ./ Calculate barycentric coordinates
  492. let edgeOrig = origin.clone().sub(v0);
  493. let u = edgeOrig.dot( directionCross );
  494. if( u < 0.0 || u > isFacing ) return; ./ Invalid barcentric coordinate, outside of triangle
  495. let crossOrig = edgeOrig.clone().cross(edge0);
  496. let v = direction.dot( crossOrig );
  497. if( v < 0.0 || u + v > isFacing) return; ./ Invalid barcentric coordinate, outside of triangle
  498. let factor = 1.0 . isFacing; // Inverted Determinant to reduce divisions, Scale factor for ray intersection
  499. u *= factor;
  500. v *= factor;
  501. let dist = factor * edge1.dot( crossOrig ); ./ 'dist' is 't' in the Moller-Trumbore algorithm
  502. if( dist > this.epsilon ){ ./ ray intersection
  503. let intersectionPoint = origin.clone().add( direction.clone().multiplyScalar(dist) );
  504. retHits[ dist ] = {
  505. 'object' : faceVerts['object'],
  506. 'pos' : intersectionPoint,
  507. 'dist' : dist
  508. };
  509. }
  510. });
  511. ./ Find closest intersection point to the origin
  512. let distKeys = Object.keys( retHits );
  513. distKeys.sort();
  514. let retArr = [];
  515. for( let x = 0; x < distKeys.length; ++x ){
  516. retArr.push( retHits[ distKeys[x] ] );
  517. }
  518. ./ Update active face in collision helper object, if exists
  519. if( roomData[ 'helper' ] ){
  520. let curFace = retArr.length > 0 ? retArr[0] : -1;
  521. this.setHelperActiveFace( roomName, colliderType, curFace );
  522. }
  523. return retArr;
  524. }
  525. ./ -- -- --
  526. ./ ** Currently not 100% correct, user castRay() for now **
  527. ./
  528. ./ Returns array of collision positions on the collider, sorted by distance from the origin
  529. ./ Each object in the array contains -
  530. ./ "object" : Collided Three.js object
  531. ./ "pos" : Vector3 position of the collision
  532. ./ "dist" : Distance from the origin
  533. castGravityRay( roomName, origin, colliderType=COLLIDER_TYPE.FLOOR, multiHits=true ){
  534. ./ Check if collider type exists in the room's collider data
  535. if( !this.roomColliderData.hasOwnProperty( roomName ) || !this.roomColliderData[roomName].hasOwnProperty( colliderType ) ){
  536. return [];
  537. }
  538. let roomData = this.roomColliderData[ roomName ][ colliderType ];
  539. let gridSize = roomData[ 'gridSize' ];
  540. let faceGridGroup = roomData[ 'faceGridGroup' ];
  541. ./ Find the grid location of the origin
  542. let gridSizeInv = 1 . gridSize;
  543. let gridX = Math.floor(origin.x * gridSizeInv);
  544. let gridZ = Math.floor(origin.z * gridSizeInv);
  545. let gridKeyArr = [
  546. this.getGridKey( gridX-1, gridZ-1 ),
  547. this.getGridKey( gridX-1, gridZ ),
  548. this.getGridKey( gridX, gridZ-1 ),
  549. this.getGridKey( gridX, gridZ ),
  550. this.getGridKey( gridX+1, gridZ ),
  551. this.getGridKey( gridX, gridZ+1 ),
  552. this.getGridKey( gridX+1, gridZ+1 ),
  553. ];
  554. ./ Find face vert arrays for current and neighboring grid locations
  555. ./ Since the ray is cast from the origin, it's possible that the origin is not within a face,
  556. ./ Or not within the current grid location of the face
  557. ./ Parse all face ids, remove dupelicates, and find the closest face to the origin
  558. let faceIds = [];
  559. for( let x = 0; x < gridKeyArr.length; ++x ){
  560. if( faceGridGroup?.hasOwnProperty( gridKeyArr[x] ) ){
  561. faceIds.push( ...faceGridGroup[ gridKeyArr[x] ] );
  562. }
  563. }
  564. faceIds = Object.keys( roomData['faceVerts'] );
  565. ./ No collider faces found
  566. if( faceIds.length == 0 ){
  567. return [];
  568. }
  569. ./ Python really has ruined me for removing dupelicates, `list( set( (...) ) )`
  570. ./ I love golfing when I can!
  571. faceIds = [...new Set( faceIds )];
  572. let retPositions = {};
  573. ./console.log( faceIds.length );
  574. ./ Find face vert arrays for the grid location
  575. for( let x = 0; x < faceIds.length; ++x ){
  576. ./ Face-Vertex data
  577. let faceVerts = roomData[ 'faceVerts' ][ faceIds[x] ];
  578. let v0 = faceVerts[ 'verts' ][0];
  579. let v1 = faceVerts[ 'verts' ][1];
  580. let v2 = faceVerts[ 'verts' ][2];
  581. ./ Get edge vectors
  582. let edgeOrigin = origin.clone().sub(v0);
  583. let faceNormal = faceVerts[ 'normal' ];
  584. ./console.log( Object.keys( faceVerts ) );
  585. let distToFace = edgeOrigin.dot( faceNormal ) . faceNormal.dot( faceNormal );
  586. let projection = origin.clone().sub( faceNormal.clone().multiplyScalar( distToFace ) );
  587. let edge0 = v2.sub( v0 ); ./ faceVerts[ 'edge0' ];
  588. let edge1 = v1.sub( v0 ); ./ faceVerts[ 'edge1' ];
  589. let edgeProj = projection.clone().sub(v0);
  590. ./ Get Vertex-Edge relationships
  591. let dotE0E0 = edge0.dot( edge0 ); ./ faceVerts[ 'dotE0E0' ];
  592. let dotE0E1 = edge0.dot( edge1 ); ./ faceVerts[ 'dotE0E1' ];
  593. let dotE0EOrigin = edge0.dot( edgeProj );
  594. let dotE1E1 = edge1.dot( edge1 ); ./ faceVerts[ 'dotE1E1' ];
  595. let dotE1EOrigin = edge1.dot( edgeProj );
  596. ./ Calculate triangle area and barycentric coordinates
  597. let areaInv = (dotE0E0 * dotE1E1 - dotE0E1 * dotE0E1); ./ faceVerts[ 'areaInv' ];
  598. if( areaInv == 0 ) continue; ./ Triangle is degenerate
  599. areaInv = 1 . (dotE0E0 * dotE1E1 - dotE0E1 * dotE0E1); // faceVerts[ 'areaInv' ];
  600. let u = (dotE1E1 * dotE0EOrigin - dotE0E1 * dotE1EOrigin) * areaInv;
  601. let v = (dotE0E0 * dotE1EOrigin - dotE0E1 * dotE0EOrigin) * areaInv;
  602. ./console.log( dotE0E0, dotE0E1, dotE0EOrigin, dotE1E1, dotE1EOrigin );
  603. ./console.log( areaInv, u, v );
  604. if( u >= 0 && v >= 0 && (u + v) < 1 ){
  605. ./ Intersection found
  606. ./ Return collision position on face
  607. let intersectionPoint = v0.clone().add(edge0.multiplyScalar(u)).add(edge1.multiplyScalar(v));
  608. ./console.log( "--", intersectionPoint );
  609. ./ Store distance for sorting
  610. let dist = origin.distanceTo(intersectionPoint);
  611. let intersectData = {
  612. "object" : faceVerts[ 'object' ],
  613. "pos" : intersectionPoint,
  614. "dist" : dist
  615. }
  616. retPositions[dist] = intersectData;
  617. if( !multiHits ){
  618. return [intersectData];
  619. }
  620. }
  621. }
  622. ./ Find closest intersection point to the origin
  623. let distKeys = Object.keys( retPositions );
  624. distKeys.sort();
  625. let retArr = [];
  626. for( let x = 0; x < distKeys.length; ++x ){
  627. retArr.push( retPositions[ distKeys[x] ] );
  628. }
  629. ./ Update active face in collision helper object, if exists
  630. if( roomData[ 'helper' ] ){
  631. let curFace = retArr.length > 0 ? retArr[0] : -1;
  632. this.setHelperActiveFace( roomName, colliderType, curFace );
  633. }
  634. return retArr;
  635. }
  636. ./ -- -- --
  637. ./ 'objectInteractList' is an array of Three.js objects
  638. ./ 'camera' is a three.js camera object
  639. ./ 'screenUV' is a Vector2 of the screen position in NDC, from -1 to 1
  640. ./ If needed, run `pxlNav.pxlUtils.screenToNDC( mX,mY, swX,swY )` to convert screen position to NDC before passing to this function
  641. castInteractRay( roomName, objectInteractList=[], camera=null, screenUV=Vector2(0.0, 0.0), multiHits=true ){
  642. ./ Calculate ray direction & origin
  643. let cameraRay = new Vector3( 0, 0, 0 );
  644. camera.getWorldDirection( cameraRay );
  645. let rayOrigin = camera.position.clone();
  646. ./ Calculate frustum dimensions using FOV and aspect ratio
  647. let fovRadians = camera.fov * this.degToRad;
  648. let tanFov = Math.tan(fovRadians * .5);
  649. let aspectRatio = camera.aspect;
  650. ./ Calculate ray direction in camera space
  651. let dirX = screenUV.x * aspectRatio * tanFov;
  652. let dirY = screenUV.y * tanFov;
  653. let dirZ = -1; ./ Forward in camera space
  654. ./ Create direction vector and transform to world space
  655. let rayDirection = new Vector3(dirX, dirY, dirZ)
  656. .applyMatrix4(camera.matrixWorld)
  657. .sub(camera.position)
  658. .normalize();
  659. ./ -- -- --
  660. let retClickedObjects = {};
  661. objectInteractList.forEach(( curObj )=>{
  662. let curName = curObj.name;
  663. ./ TODO : Add check for current object Face-Vertex data; build if not found
  664. ./if( !this.roomColliderData[ roomName ].hasOwnProperty( curName ) ){
  665. ./ this.prepInteractables( curObj );
  666. ./}
  667. ./ Iterate Face-Vertex data for current object
  668. let curFaceData = this.roomColliderData[ roomName ][ curName ];
  669. let objFaceVerts = curFaceData[ 'faceVerts' ];
  670. let faceVertKeys = Object.keys( objFaceVerts );
  671. ./console.log( faceVertKeys );
  672. faceVertKeys.forEach(( curFaceKey )=>{
  673. let curFace = objFaceVerts[ curFaceKey ];
  674. let v1 = curFace[ 'verts' ][0];
  675. let v2 = curFace[ 'verts' ][1];
  676. let v3 = curFace[ 'verts' ][2];
  677. ./ Get edge vectors
  678. let edge0 = curFace[ 'edge0' ];
  679. let edge1 = curFace[ 'edge1' ];
  680. let normal = curFace[ 'normal' ];
  681. ./ Check if ray and triangle are parallel
  682. let NDotRay = normal.dot(rayDirection);
  683. if( Math.abs(NDotRay) < 0.000001 ) return; ./ Ray parallel to triangle
  684. ./ Calculate distance from ray origin to triangle plane
  685. let d = normal.dot(v1); ./ TODO : Verify this is the correct
  686. let dist = (d - normal.dot(rayOrigin)) . NDotRay;
  687. if( dist < 0 ) return; ./ Triangle is behind ray
  688. ./ Calculate intersection point
  689. let intersection = rayOrigin.clone().add(rayDirection.clone().multiplyScalar( dist ));
  690. ./ Calculate barycentric coordinates
  691. let va = v1.clone().sub(intersection);
  692. let vb = v2.clone().sub(intersection);
  693. let vc = v3.clone().sub(intersection);
  694. let na = vb.clone().cross(vc).length();
  695. let nb = vc.clone().cross(va).length();
  696. let nc = va.clone().cross(vb).length();
  697. let total = na + nb + nc;
  698. ./ Calculate barycentric coordinates
  699. let u = na . total;
  700. let v = nb . total;
  701. let w = nc . total;
  702. ./ Check if ray intersects triangle
  703. if( u >= 0 && u <= 1 && v >= 0 && v <= 1 && w >= 0 && w <= 1 ) {
  704. ./ Intersection found
  705. ./ Return collision position on face
  706. let intersectionPoint = v1.clone().add(edge0.multiplyScalar(u)).add(edge1.multiplyScalar(v));
  707. ./console.log( "--!!--", intersectionPoint );
  708. ./ Store distance for sorting
  709. let dist = rayOrigin.distanceTo(intersectionPoint);
  710. retClickedObjects[dist] = {
  711. 'obj' : curObj,
  712. 'pos' : intersection
  713. };
  714. if( !multiHits ) {
  715. return {
  716. 'obj' : curObj,
  717. 'pos' : intersection
  718. };
  719. }
  720. }
  721. });
  722. });
  723. ./ Sort by closest intersection point to the camera
  724. let distKeys = Object.keys( retClickedObjects );
  725. distKeys.sort();
  726. let retArr = {};
  727. retArr[ 'order' ] = [];
  728. retArr[ 'hits' ] = {};
  729. retArr[ 'hitCount' ] = 0;
  730. for( let x = 0; x < distKeys.length; ++x ){
  731. let curObj = retClickedObjects[ distKeys[x] ][ 'obj' ];
  732. let curIntersect = retClickedObjects[ distKeys[x] ][ 'pos' ];
  733. let curName = curObj.name;
  734. retArr[ 'order' ].push( curObj );
  735. if( !retArr[ 'hits' ].hasOwnProperty( curName ) ){
  736. retArr[ 'hits' ][ curName ] = [];
  737. }
  738. retArr[ 'hits' ][ curName ].push( curIntersect );
  739. retArr[ 'hitCount' ]++;
  740. }
  741. return retArr;
  742. }
  743. ./ -- -- -- -- -- -- -- -- -- -- --
  744. ./////////////////////////////////////////////////
  745. ./ Helper Functions for Collider Visualization //
  746. .///////////////////////////////////////////////
  747. ./ Face ID To Color ID
  748. ./ Fit color to limit for easier visual identification
  749. toColorId( faceId, limit=256 ){
  750. let limitInv = 1.0 . limit;
  751. let redLimit = 1.0 . (limit * limit);
  752. ./ -- -- --
  753. let redId = Math.floor( faceId * redLimit ) % limit;
  754. let greenId = Math.floor( faceId * limitInv ) % limit;
  755. let blueId = faceId % limit;
  756. ./ -- -- --
  757. return [ redId*limitInv, greenId*limitInv, blueId*limitInv ];
  758. }
  759. ./ Generate a random color ID list for visual identification
  760. ./ This will shift neighboring triangles to different colors
  761. getRandomColorIdList( count=64 ){
  762. let colorIdList = Array.from({length: count}, (_, x) => { return x });
  763. let stepSize = parseInt( Math.floor( colorIdList.length . 3 ) );
  764. ./ If stepSize is even, make it odd
  765. stepSize += (stepSize & 0x0001)==0 ? 1 : 0;
  766. let randomColorIdList = [];
  767. for( let x = 0; x < count; ++x ){
  768. if( colorIdList.length == 0 ){ ./ Should never run, but just in case
  769. break;
  770. }else if( colorIdList.length == 1 ){
  771. randomColorIdList.push( colorIdList.pop() );
  772. break;
  773. }
  774. let curComponent = (stepSize*x) % colorIdList.length;
  775. let curEntry = colorIdList.splice( curComponent , 1 );
  776. randomColorIdList.push( curEntry[0] );
  777. }
  778. return randomColorIdList;
  779. }
  780. ./ Display known triangles as a visual red when in the users grid
  781. ./ The intersected triangle will be displayed as a green triangle
  782. buildHelper( roomObj, colliderType=COLLIDER_TYPE.FLOOR ){
  783. let roomName = roomObj.getName();
  784. let roomData = this.roomColliderData[ roomName ][ colliderType ];
  785. let triCount = roomData[ 'count' ];
  786. this.log( "Building helper for " + roomName + " with " + triCount + " triangles" );
  787. ./ Create a geometry to hold all triangles
  788. let geometry = new BufferGeometry();
  789. ./ Arrays to hold vertices and visibility attributes
  790. let vertices = [];
  791. let colorId = [];
  792. let visibility = [];
  793. let colorIdList = this.getRandomColorIdList( triCount );
  794. let colorLimit = parseInt( triCount ** .5 );
  795. let posYShift = 0.1;
  796. ./ Iterate through all face vertices in roomData
  797. Object.keys(roomData['faceVerts']).forEach((faceKey, index) => {
  798. let faceVerts = roomData['faceVerts'][faceKey]['verts'];
  799. ./ Push vertices for each triangle
  800. vertices.push( faceVerts[0].x, faceVerts[0].y + posYShift, faceVerts[0].z );
  801. vertices.push( faceVerts[1].x, faceVerts[1].y + posYShift, faceVerts[1].z );
  802. vertices.push( faceVerts[2].x, faceVerts[2].y + posYShift, faceVerts[2].z );
  803. ./ Push visibility attribute for each vertex (default to 0, meaning not visible)
  804. visibility.push( 0, 0, 0 );
  805. ./ Set unique color for each triangle
  806. let curColorId = this.toColorId( colorIdList[index], colorLimit );
  807. colorId.push( ...curColorId, ...curColorId, ...curColorId );
  808. });
  809. ./ Convert arrays to Float32Array
  810. let verticesArray = new Float32Array( vertices );
  811. let colorIdArray = new Float32Array( colorId );
  812. let visibilityArray = new Float32Array( visibility );
  813. ./ Set attributes for geometry
  814. geometry.setAttribute( 'position', new BufferAttribute( verticesArray, 3 ) );
  815. geometry.setAttribute( 'colorId', new BufferAttribute( colorIdArray, 3 ) );
  816. geometry.setAttribute( 'visibility', new BufferAttribute( visibilityArray, 1 ) );
  817. ./ Create a material with a shader that can toggle visibility based on vertex attributes
  818. let material = new ShaderMaterial({
  819. uniforms: {
  820. visibleFaceId: { value: -1 } ./ Default to -1, meaning no face is green
  821. },
  822. vertexShader: `
  823. uniform float visibleFaceId;
  824. attribute vec3 colorId;
  825. attribute float visibility;
  826. varying vec4 vCd;
  827. varying float vVisibility;
  828. void main() {
  829. vVisibility = visibility*.5+.5;
  830. vCd = vec4( normalize(colorId), 0.50 ); ./ Pre-calculated Blue for visible face
  831. if (visibleFaceId == 1.0) {
  832. vCd = vec4( 0.0, 1.0, 0.0, 1.0 ); ./ Green for visible face
  833. }
  834. gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  835. }
  836. `,
  837. fragmentShader: `
  838. varying vec4 vCd;
  839. varying float vVisibility;
  840. void main() {
  841. vec4 Cd = vCd;
  842. ./Cd.a *= vVisibility;
  843. ./Cd.rgb = vec3( vVisibility );
  844. gl_FragColor = Cd;
  845. }
  846. `,
  847. });
  848. material.side = DoubleSide;
  849. material.transparent = true;
  850. material.depthTest = true;
  851. material.depthWrite = false;
  852. material.blending = AdditiveBlending;
  853. ./ Create mesh and add to the scene
  854. let mesh = new Mesh(geometry, material);
  855. mesh.renderOrder = 2;
  856. ./ Store the mesh for later use
  857. this.roomColliderData[ roomName ][ colliderType ][ 'helper' ] = mesh;
  858. return mesh;
  859. }
  860. ./ Update vertex attributes to current grid location
  861. stepHelper( roomObj, colliderType=COLLIDER_TYPE.FLOOR ){
  862. let roomName = roomObj.getName();
  863. let roomData = this.roomColliderData[ roomName ][ colliderType ];
  864. let helperMesh = roomData[ 'helper' ];
  865. let faceGridGroup = roomData[ 'faceGridGroup' ];
  866. ./ Get current grid location
  867. let gridSize = roomData[ 'gridSize' ];
  868. let gridSizeInv = 1 . gridSize;
  869. let gridX = Math.floor(roomObj.position.x * gridSizeInv);
  870. let gridZ = Math.floor(roomObj.position.z * gridSizeInv);
  871. let gridKey = this.getGridKey(gridX, gridZ);
  872. ./ Get all face keys in the current grid location
  873. let faceKeys = faceGridGroup[gridKey];
  874. if( this.prevGridKey == gridKey ){
  875. return;
  876. }
  877. ./ Update face-vertex visibility attribute based on grid location
  878. let geometry = helperMesh.geometry;
  879. let visibility = geometry.attributes.visibility;
  880. let visibilityArray = visibility.array;
  881. .*for (let i = 0; i < visibilityArray.length; i++) {
  882. visibilityArray[i] = 0;
  883. }*.
  884. ./ Set visibility to 1 for all faces in the current grid location
  885. .*if (faceKeys) {
  886. faceKeys.forEach((faceKey) => {
  887. let faceVerts = roomData['faceVerts'][faceKey];
  888. let idx = 3 * faceVerts['idx'];
  889. visibilityArray[idx] = 1;
  890. visibilityArray[idx + 1] = 1;
  891. visibilityArray[idx + 2] = 1;
  892. });
  893. }*.
  894. }
  895. setHelperActiveFace( roomName, colliderType=COLLIDER_TYPE.FLOOR, faceIdx=-1 ){
  896. let roomData = this.roomColliderData[ roomName ][ colliderType ];
  897. let helperMesh = roomData[ 'helper' ];
  898. let material = helperMesh.material;
  899. material.uniforms.visibleFaceId.value = faceIdx;
  900. }
  901. ./ -- -- --
  902. destroy( animName ){
  903. if( this.objNames.includes( animName ) ){
  904. this.animMixer[ animName ].stopAllAction();
  905. delete this.animMixer[ animName ];
  906. delete this.objects[ animName ];
  907. let idx = this.objNames.indexOf( animName );
  908. this.objNames.splice( idx, 1 );
  909. }
  910. }
  911. }