From 87948e3fb1d14ad5c8e27b67def8e479780b0292 Mon Sep 17 00:00:00 2001 From: Paul Brunt Date: Sat, 5 Dec 2020 10:24:28 +0000 Subject: [PATCH] Simple collision example --- examples/files.json | 3 +- examples/js/math/Capsule.js | 137 +++++ examples/js/math/Octree.js | 457 +++++++++++++++++ examples/jsm/math/Capsule.d.ts | 23 + examples/jsm/math/Capsule.js | 143 ++++++ examples/jsm/math/Octree.d.ts | 32 ++ examples/jsm/math/Octree.js | 469 ++++++++++++++++++ examples/misc_octree_collisions.html | 373 ++++++++++++++ examples/models/gltf/collision-world.glb | Bin 0 -> 94556 bytes .../screenshots/misc_octree_collisions.jpg | Bin 0 -> 29653 bytes utils/modularize.js | 2 + 11 files changed, 1638 insertions(+), 1 deletion(-) create mode 100644 examples/js/math/Capsule.js create mode 100644 examples/js/math/Octree.js create mode 100644 examples/jsm/math/Capsule.d.ts create mode 100644 examples/jsm/math/Capsule.js create mode 100644 examples/jsm/math/Octree.d.ts create mode 100644 examples/jsm/math/Octree.js create mode 100644 examples/misc_octree_collisions.html create mode 100644 examples/models/gltf/collision-world.glb create mode 100644 examples/screenshots/misc_octree_collisions.jpg diff --git a/examples/files.json b/examples/files.json index cf8be90ba78d15..3c0d254df1dfae 100644 --- a/examples/files.json +++ b/examples/files.json @@ -384,7 +384,8 @@ "misc_exporter_ply", "misc_exporter_stl", "misc_legacy", - "misc_lookat" + "misc_lookat", + "misc_octree_collisions" ], "css2d": [ "css2d_label" diff --git a/examples/js/math/Capsule.js b/examples/js/math/Capsule.js new file mode 100644 index 00000000000000..c979e9112047e1 --- /dev/null +++ b/examples/js/math/Capsule.js @@ -0,0 +1,137 @@ + +THREE.Capsule = ( function () { + + var _v1 = new THREE.Vector3(); + var _v2 = new THREE.Vector3(); + var _v3 = new THREE.Vector3(); + + var EPS = 1e-10; + + function Capsule( start, end, radius ) { + + this.start = start == undefined ? new THREE.Vector3( 0, 0, 0 ) : start; + this.end = end == undefined ? new THREE.Vector3( 0, 1, 0 ) : end; + this.radius = radius == undefined ? 1 : radius; + + } + + Object.assign( Capsule.prototype, { + + clone: function () { + + return new Capsule( this.start.clone(), this.end.clone(), this.radius ); + + }, + + set: function ( start, end, radius ) { + + this.start.copy( start ); + this.end.copy( end ); + this.radius = radius; + + }, + + copy: function ( capsule ) { + + this.start.copy( capsule.start ); + this.end.copy( capsule.end ); + this.radius = capsule.radius; + + }, + + getCenter: function ( target ) { + + return target.copy( this.end ).add( this.start ).multiplyScalar( 0.5 ); + + }, + + translate: function ( v ) { + + this.start.add( v ); + this.end.add( v ); + + }, + + checkAABBAxis: function ( p1x, p1y, p2x, p2y, minx, maxx, miny, maxy, radius ) { + + return ( + ( minx - p1x < radius || minx - p2x < radius ) && + ( p1x - maxx < radius || p2x - maxx < radius ) && + ( miny - p1y < radius || miny - p2y < radius ) && + ( p1y - maxy < radius || p2y - maxy < radius ) + ); + + }, + + intersectsBox: function ( box ) { + + return ( + this.checkAABBAxis( + this.start.x, this.start.y, this.end.x, this.end.y, + box.min.x, box.max.x, box.min.y, box.max.y, + this.radius ) && + this.checkAABBAxis( + this.start.x, this.start.z, this.end.x, this.end.z, + box.min.x, box.max.x, box.min.z, box.max.z, + this.radius ) && + this.checkAABBAxis( + this.start.y, this.start.z, this.end.y, this.end.z, + box.min.y, box.max.y, box.min.z, box.max.z, + this.radius ) + ); + + }, + + lineLineMinimumPoints: function ( line1, line2 ) { + + var r = _v1.copy( line1.end ).sub( line1.start ); + var s = _v2.copy( line2.end ).sub( line2.start ); + var w = _v3.copy( line2.start ).sub( line1.start ); + + var a = r.dot( s ), + b = r.dot( r ), + c = s.dot( s ), + d = s.dot( w ), + e = r.dot( w ); + + var t1, t2, divisor = b * c - a * a; + + if ( Math.abs( divisor ) < EPS ) { + + var d1 = - d / c; + var d2 = ( a - d ) / c; + + if ( Math.abs( d1 - 0.5 ) < Math.abs( d2 - 0.5 ) ) { + + t1 = 0; + t2 = d1; + + } else { + + t1 = 1; + t2 = d2; + + } + + } else { + + t1 = ( d * a + e * c ) / divisor; + t2 = ( t1 * a - d ) / c; + + } + + t2 = Math.max( 0, Math.min( 1, t2 ) ); + t1 = Math.max( 0, Math.min( 1, t1 ) ); + + var point1 = r.multiplyScalar( t1 ).add( line1.start ); + var point2 = s.multiplyScalar( t2 ).add( line2.start ); + + return [ point1, point2 ]; + + } + + } ); + + return Capsule; + +} )(); diff --git a/examples/js/math/Octree.js b/examples/js/math/Octree.js new file mode 100644 index 00000000000000..82c3b467a98521 --- /dev/null +++ b/examples/js/math/Octree.js @@ -0,0 +1,457 @@ + +THREE.Octree = ( function () { + + var _v1 = new THREE.Vector3(); + var _v2 = new THREE.Vector3(); + var _plane = new THREE.Plane(); + var _line1 = new THREE.Line3(); + var _line2 = new THREE.Line3(); + var _sphere = new THREE.Sphere(); + var _capsule = new THREE.Capsule(); + + function Octree( box ) { + + this.triangles = []; + this.box = box; + this.subTrees = []; + + } + + Object.assign( Octree.prototype, { + + addTriangle: function ( triangle ) { + + if ( ! this.bounds ) this.bounds = new THREE.Box3(); + + this.bounds.min.x = Math.min( this.bounds.min.x, triangle.a.x, triangle.b.x, triangle.c.x ); + this.bounds.min.y = Math.min( this.bounds.min.y, triangle.a.y, triangle.b.y, triangle.c.y ); + this.bounds.min.z = Math.min( this.bounds.min.z, triangle.a.z, triangle.b.z, triangle.c.z ); + this.bounds.max.x = Math.max( this.bounds.max.x, triangle.a.x, triangle.b.x, triangle.c.x ); + this.bounds.max.y = Math.max( this.bounds.max.y, triangle.a.y, triangle.b.y, triangle.c.y ); + this.bounds.max.z = Math.max( this.bounds.max.z, triangle.a.z, triangle.b.z, triangle.c.z ); + + this.triangles.push( triangle ); + + return this; + + }, + + calcBox: function () { + + this.box = this.bounds.clone(); + + // offset small ammount to account for regular grid + this.box.min.x -= 0.01; + this.box.min.y -= 0.01; + this.box.min.z -= 0.01; + + return this; + + }, + + split: function ( level ) { + + if ( ! this.box ) return; + + var subTrees = [], + halfsize = _v2.copy( this.box.max ).sub( this.box.min ).multiplyScalar( 0.5 ), + box, v, triangle; + + for ( var x = 0; x < 2; x ++ ) { + + for ( var y = 0; y < 2; y ++ ) { + + for ( var z = 0; z < 2; z ++ ) { + + box = new THREE.Box3(); + v = _v1.set( x, y, z ); + + box.min.copy( this.box.min ).add( v.multiply( halfsize ) ); + box.max.copy( box.min ).add( halfsize ); + + subTrees.push( new Octree( box ) ); + + } + + } + + } + + while ( triangle = this.triangles.pop() ) { + + for ( var i = 0; i < subTrees.length; i ++ ) { + + if ( subTrees[ i ].box.intersectsTriangle( triangle ) ) { + + subTrees[ i ].triangles.push( triangle ); + + } + + } + + } + + for ( var i = 0; i < subTrees.length; i ++ ) { + + var len = subTrees[ i ].triangles.length; + + if ( len > 8 && level < 16 ) { + + subTrees[ i ].split( level + 1 ); + + } + + if ( len != 0 ) { + + this.subTrees.push( subTrees[ i ] ); + + } + + } + + return this; + + }, + + build: function () { + + this.calcBox(); + this.split( 0 ); + + return this; + + }, + + getRayTriangles: function ( ray, triangles ) { + + for ( var i = 0; i < this.subTrees.length; i ++ ) { + + var subTree = this.subTrees[ i ]; + if ( ! ray.intersectsBox( subTree.box ) ) continue; + + if ( subTree.triangles.length > 0 ) { + + for ( var j = 0; j < subTree.triangles.length; j ++ ) { + + if ( triangles.indexOf( subTree.triangles[ j ] ) === - 1 ) triangles.push( subTree.triangles[ j ] ); + + } + + } else { + + subTree.getRayTriangles( ray, triangles ); + + } + + } + + return triangles; + + }, + + triangleCapsuleIntersect: function ( capsule, triangle ) { + + var point1, point2, line1, line2; + + triangle.getPlane( _plane ); + + var d1 = _plane.distanceToPoint( capsule.start ) - capsule.radius; + var d2 = _plane.distanceToPoint( capsule.end ) - capsule.radius; + + if ( ( d1 > 0 && d2 > 0 ) || ( d1 < - capsule.radius && d2 < - capsule.radius ) ) { + + return false; + + } + + var delta = Math.abs( d1 / ( Math.abs( d1 ) + Math.abs( d2 ) ) ); + var intersectPoint = _v1.copy( capsule.start ).lerp( capsule.end, delta ); + + if ( triangle.containsPoint( intersectPoint ) ) { + + return { normal: _plane.normal.clone(), point: intersectPoint.clone(), depth: Math.abs( Math.min( d1, d2 ) ) }; + + } + + var r2 = capsule.radius * capsule.radius; + + line1 = _line1.set( capsule.start, capsule.end ); + + var lines = [ + [ triangle.a, triangle.b ], + [ triangle.b, triangle.c ], + [ triangle.c, triangle.a ] + ]; + + for ( var i = 0; i < lines.length; i ++ ) { + + line2 = _line2.set( lines[ i ][ 0 ], lines[ i ][ 1 ] ); + + [ point1, point2 ] = capsule.lineLineMinimumPoints( line1, line2 ); + + if ( point1.distanceToSquared( point2 ) < r2 ) { + + return { normal: point1.clone().sub( point2 ).normalize(), point: point2.clone(), depth: capsule.radius - point1.distanceTo( point2 ) }; + + } + + } + + return false; + + }, + + triangleSphereIntersect: function ( sphere, triangle ) { + + triangle.getPlane( _plane ); + + if ( ! sphere.intersectsPlane( _plane ) ) return false; + + var depth = Math.abs( _plane.distanceToSphere( sphere ) ); + var r2 = sphere.radius * sphere.radius - depth * depth; + + var plainPoint = _plane.projectPoint( sphere.center, _v1 ); + + if ( triangle.containsPoint( sphere.center ) ) { + + return { normal: _plane.normal.clone(), point: plainPoint.clone(), depth: Math.abs( _plane.distanceToSphere( sphere ) ) }; + + } + + var lines = [ + [ triangle.a, triangle.b ], + [ triangle.b, triangle.c ], + [ triangle.c, triangle.a ] + ]; + + for ( var i = 0; i < lines.length; i ++ ) { + + _line1.set( lines[ i ][ 0 ], lines[ i ][ 1 ] ); + _line1.closestPointToPoint( plainPoint, true, _v2 ); + + var d = _v2.distanceToSquared( sphere.center ); + + if ( d < r2 ) { + + return { normal: sphere.center.clone().sub( _v2 ).normalize(), point: _v2.clone(), depth: sphere.radius - Math.sqrt( d ) }; + + } + + } + + return false; + + }, + + getSphereTriangles: function ( sphere, triangles ) { + + for ( var i = 0; i < this.subTrees.length; i ++ ) { + + var subTree = this.subTrees[ i ]; + + if ( ! sphere.intersectsBox( subTree.box ) ) continue; + + if ( subTree.triangles.length > 0 ) { + + for ( var j = 0; j < subTree.triangles.length; j ++ ) { + + if ( triangles.indexOf( subTree.triangles[ j ] ) === - 1 ) triangles.push( subTree.triangles[ j ] ); + + } + + } else { + + subTree.getSphereTriangles( sphere, triangles ); + + } + + } + + }, + + getCapsuleTriangles: function ( capsule, triangles ) { + + for ( var i = 0; i < this.subTrees.length; i ++ ) { + + var subTree = this.subTrees[ i ]; + + if ( ! capsule.intersectsBox( subTree.box ) ) continue; + + if ( subTree.triangles.length > 0 ) { + + for ( var j = 0; j < subTree.triangles.length; j ++ ) { + + if ( triangles.indexOf( subTree.triangles[ j ] ) === - 1 ) triangles.push( subTree.triangles[ j ] ); + + } + + } else { + + subTree.getCapsuleTriangles( capsule, triangles ); + + } + + } + + }, + + sphereIntersect( sphere ) { + + _sphere.copy( sphere ); + + var triangles = [], result, hit = false; + + this.getSphereTriangles( sphere, triangles ); + + for ( var i = 0; i < triangles.length; i ++ ) { + + if ( result = this.triangleSphereIntersect( _sphere, triangles[ i ] ) ) { + + hit = true; + + _sphere.center.add( result.normal.multiplyScalar( result.depth ) ); + + } + + } + + if ( hit ) { + + var collisionVector = _sphere.center.clone().sub( sphere.center ); + var depth = collisionVector.length(); + + return { normal: collisionVector.normalize(), depth: depth }; + + } + + return false; + + }, + + capsuleIntersect: function ( capsule ) { + + _capsule.copy( capsule ); + + var triangles = [], result, hit = false; + + this.getCapsuleTriangles( _capsule, triangles ); + + for ( var i = 0; i < triangles.length; i ++ ) { + + if ( result = this.triangleCapsuleIntersect( _capsule, triangles[ i ] ) ) { + + hit = true; + + _capsule.translate( result.normal.multiplyScalar( result.depth ) ); + + } + + } + + if ( hit ) { + + var collisionVector = _capsule.getCenter( new THREE.Vector3() ).sub( capsule.getCenter( _v1 ) ); + var depth = collisionVector.length(); + + return { normal: collisionVector.normalize(), depth: depth }; + + } + + return false; + + }, + + rayIntersect: function ( ray ) { + + if ( ray.direction.length() === 0 ) return; + + var triangles = [], triangle, position, + distance = 1e100, + result; + + this.getRayTriangles( ray, triangles ); + + for ( var i = 0; i < triangles.length; i ++ ) { + + result = ray.intersectTriangle( triangles[ i ].a, triangles[ i ].b, triangles[ i ].c, true, _v1 ); + + if ( result ) { + + var newdistance = result.sub( ray.origin ).length(); + + if ( distance > newdistance ) { + + position = result.clone().add( ray.origin ); + distance = newdistance; + triangle = triangles[ i ]; + + } + + } + + } + + return distance < 1e100 ? { distance: distance, triangle: triangle, position: position } : false; + + }, + + fromGraphNode: function ( group ) { + + group.traverse( ( obj ) => { + + if ( obj.type === 'Mesh' ) { + + obj.updateMatrix(); + obj.updateWorldMatrix(); + + var geometry, isTemp = false; + + if ( obj.geometry.index ) { + + isTemp = true; + geometry = obj.geometry.clone().toNonIndexed(); + + } else { + + geometry = obj.geometry; + + } + + var positions = geometry.attributes.position.array; + var transform = obj.matrixWorld; + + for ( var i = 0; i < positions.length; i += 9 ) { + + var v1 = new THREE.Vector3( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); + var v2 = new THREE.Vector3( positions[ i + 3 ], positions[ i + 4 ], positions[ i + 5 ] ); + var v3 = new THREE.Vector3( positions[ i + 6 ], positions[ i + 7 ], positions[ i + 8 ] ); + + v1.applyMatrix4( transform ); + v2.applyMatrix4( transform ); + v3.applyMatrix4( transform ); + + this.addTriangle( new THREE.Triangle( v1, v2, v3 ) ); + + } + + if ( isTemp ) { + + geometry.dispose(); + + } + + } + + } ); + + this.build(); + + return this; + + } + + } ); + + return Octree; + +} )(); diff --git a/examples/jsm/math/Capsule.d.ts b/examples/jsm/math/Capsule.d.ts new file mode 100644 index 00000000000000..4d77b5f942d8a2 --- /dev/null +++ b/examples/jsm/math/Capsule.d.ts @@ -0,0 +1,23 @@ +import { + Vector3, + Line3, + Box3 +} from '../../../src/Three'; + +export class Capsule { + + constructor( start?: Vector3, end?: Vector3, radius?: number ); + start: Vector3; + end: Vector3; + radius: number; + + set( start: Vector3, end: Vector3, radius: number ): this; + clone(): Capsule; + copy( capsule: Capsule ): this; + getCenter( target: number ): Vector3; + translate( v: Vector3 ): this; + checkAABBAxis( p1x: number, p1y: number, p2x: number, p2y: number, minx: number, maxx: number, miny: number, maxy: number, radius: number ): boolean; + intersectsBox( box: Box3 ): boolean; + lineLineMinimumPoints( line1: Line3, line2: Line3 ): Vector3[]; + +} diff --git a/examples/jsm/math/Capsule.js b/examples/jsm/math/Capsule.js new file mode 100644 index 00000000000000..807bcdd74754d3 --- /dev/null +++ b/examples/jsm/math/Capsule.js @@ -0,0 +1,143 @@ +import { + Vector3 +} from "../../../build/three.module.js"; + + +var Capsule = ( function () { + + var _v1 = new Vector3(); + var _v2 = new Vector3(); + var _v3 = new Vector3(); + + var EPS = 1e-10; + + function Capsule( start, end, radius ) { + + this.start = start == undefined ? new Vector3( 0, 0, 0 ) : start; + this.end = end == undefined ? new Vector3( 0, 1, 0 ) : end; + this.radius = radius == undefined ? 1 : radius; + + } + + Object.assign( Capsule.prototype, { + + clone: function () { + + return new Capsule( this.start.clone(), this.end.clone(), this.radius ); + + }, + + set: function ( start, end, radius ) { + + this.start.copy( start ); + this.end.copy( end ); + this.radius = radius; + + }, + + copy: function ( capsule ) { + + this.start.copy( capsule.start ); + this.end.copy( capsule.end ); + this.radius = capsule.radius; + + }, + + getCenter: function ( target ) { + + return target.copy( this.end ).add( this.start ).multiplyScalar( 0.5 ); + + }, + + translate: function ( v ) { + + this.start.add( v ); + this.end.add( v ); + + }, + + checkAABBAxis: function ( p1x, p1y, p2x, p2y, minx, maxx, miny, maxy, radius ) { + + return ( + ( minx - p1x < radius || minx - p2x < radius ) && + ( p1x - maxx < radius || p2x - maxx < radius ) && + ( miny - p1y < radius || miny - p2y < radius ) && + ( p1y - maxy < radius || p2y - maxy < radius ) + ); + + }, + + intersectsBox: function ( box ) { + + return ( + this.checkAABBAxis( + this.start.x, this.start.y, this.end.x, this.end.y, + box.min.x, box.max.x, box.min.y, box.max.y, + this.radius ) && + this.checkAABBAxis( + this.start.x, this.start.z, this.end.x, this.end.z, + box.min.x, box.max.x, box.min.z, box.max.z, + this.radius ) && + this.checkAABBAxis( + this.start.y, this.start.z, this.end.y, this.end.z, + box.min.y, box.max.y, box.min.z, box.max.z, + this.radius ) + ); + + }, + + lineLineMinimumPoints: function ( line1, line2 ) { + + var r = _v1.copy( line1.end ).sub( line1.start ); + var s = _v2.copy( line2.end ).sub( line2.start ); + var w = _v3.copy( line2.start ).sub( line1.start ); + + var a = r.dot( s ), + b = r.dot( r ), + c = s.dot( s ), + d = s.dot( w ), + e = r.dot( w ); + + var t1, t2, divisor = b * c - a * a; + + if ( Math.abs( divisor ) < EPS ) { + + var d1 = - d / c; + var d2 = ( a - d ) / c; + + if ( Math.abs( d1 - 0.5 ) < Math.abs( d2 - 0.5 ) ) { + + t1 = 0; + t2 = d1; + + } else { + + t1 = 1; + t2 = d2; + + } + + } else { + + t1 = ( d * a + e * c ) / divisor; + t2 = ( t1 * a - d ) / c; + + } + + t2 = Math.max( 0, Math.min( 1, t2 ) ); + t1 = Math.max( 0, Math.min( 1, t1 ) ); + + var point1 = r.multiplyScalar( t1 ).add( line1.start ); + var point2 = s.multiplyScalar( t2 ).add( line2.start ); + + return [ point1, point2 ]; + + } + + } ); + + return Capsule; + +} )(); + +export { Capsule }; diff --git a/examples/jsm/math/Octree.d.ts b/examples/jsm/math/Octree.d.ts new file mode 100644 index 00000000000000..315e3cc22528be --- /dev/null +++ b/examples/jsm/math/Octree.d.ts @@ -0,0 +1,32 @@ +import { + Triangle, + Box3, + Ray, + Sphere, + Object3D +} from '../../../src/Three'; + +import { Capsule } from './Capsule'; + +export class Octree { + + constructor( box?: Box3 ); + triangles: Triangle[]; + box: Box3; + subTrees: Octree[]; + + addTriangle( triangle: Triangle ): this; + calcBox(): this; + split( level: number ): this; + build(): this; + getRayTriangles( ray: Ray, triangles: Triangle[] ): Triangle[]; + triangleCapsuleIntersect( capsule: Capsule, triangle: Triangle ): any; + triangleSphereIntersect( sphere: Sphere, triangle: Triangle ): any; + getSphereTriangles( sphere: Sphere, triangles: Triangle[] ): Triangle[]; + getCapsuleTriangles( capsule: Capsule, triangles: Triangle[] ): Triangle[]; + sphereIntersect( sphere: Sphere ): any; + capsuleIntersect( capsule: Capsule ): any; + rayIntersect( ray: Ray ): any; + fromGraphNode( group: Object3D ): this; + +} diff --git a/examples/jsm/math/Octree.js b/examples/jsm/math/Octree.js new file mode 100644 index 00000000000000..70746051a2b6a3 --- /dev/null +++ b/examples/jsm/math/Octree.js @@ -0,0 +1,469 @@ +import { + Box3, + Line3, + Plane, + Sphere, + Triangle, + Vector3 +} from "../../../build/three.module.js"; +import { Capsule } from "../math/Capsule.js"; + + +var Octree = ( function () { + + var _v1 = new Vector3(); + var _v2 = new Vector3(); + var _plane = new Plane(); + var _line1 = new Line3(); + var _line2 = new Line3(); + var _sphere = new Sphere(); + var _capsule = new Capsule(); + + function Octree( box ) { + + this.triangles = []; + this.box = box; + this.subTrees = []; + + } + + Object.assign( Octree.prototype, { + + addTriangle: function ( triangle ) { + + if ( ! this.bounds ) this.bounds = new Box3(); + + this.bounds.min.x = Math.min( this.bounds.min.x, triangle.a.x, triangle.b.x, triangle.c.x ); + this.bounds.min.y = Math.min( this.bounds.min.y, triangle.a.y, triangle.b.y, triangle.c.y ); + this.bounds.min.z = Math.min( this.bounds.min.z, triangle.a.z, triangle.b.z, triangle.c.z ); + this.bounds.max.x = Math.max( this.bounds.max.x, triangle.a.x, triangle.b.x, triangle.c.x ); + this.bounds.max.y = Math.max( this.bounds.max.y, triangle.a.y, triangle.b.y, triangle.c.y ); + this.bounds.max.z = Math.max( this.bounds.max.z, triangle.a.z, triangle.b.z, triangle.c.z ); + + this.triangles.push( triangle ); + + return this; + + }, + + calcBox: function () { + + this.box = this.bounds.clone(); + + // offset small ammount to account for regular grid + this.box.min.x -= 0.01; + this.box.min.y -= 0.01; + this.box.min.z -= 0.01; + + return this; + + }, + + split: function ( level ) { + + if ( ! this.box ) return; + + var subTrees = [], + halfsize = _v2.copy( this.box.max ).sub( this.box.min ).multiplyScalar( 0.5 ), + box, v, triangle; + + for ( var x = 0; x < 2; x ++ ) { + + for ( var y = 0; y < 2; y ++ ) { + + for ( var z = 0; z < 2; z ++ ) { + + box = new Box3(); + v = _v1.set( x, y, z ); + + box.min.copy( this.box.min ).add( v.multiply( halfsize ) ); + box.max.copy( box.min ).add( halfsize ); + + subTrees.push( new Octree( box ) ); + + } + + } + + } + + while ( triangle = this.triangles.pop() ) { + + for ( var i = 0; i < subTrees.length; i ++ ) { + + if ( subTrees[ i ].box.intersectsTriangle( triangle ) ) { + + subTrees[ i ].triangles.push( triangle ); + + } + + } + + } + + for ( var i = 0; i < subTrees.length; i ++ ) { + + var len = subTrees[ i ].triangles.length; + + if ( len > 8 && level < 16 ) { + + subTrees[ i ].split( level + 1 ); + + } + + if ( len != 0 ) { + + this.subTrees.push( subTrees[ i ] ); + + } + + } + + return this; + + }, + + build: function () { + + this.calcBox(); + this.split( 0 ); + + return this; + + }, + + getRayTriangles: function ( ray, triangles ) { + + for ( var i = 0; i < this.subTrees.length; i ++ ) { + + var subTree = this.subTrees[ i ]; + if ( ! ray.intersectsBox( subTree.box ) ) continue; + + if ( subTree.triangles.length > 0 ) { + + for ( var j = 0; j < subTree.triangles.length; j ++ ) { + + if ( triangles.indexOf( subTree.triangles[ j ] ) === - 1 ) triangles.push( subTree.triangles[ j ] ); + + } + + } else { + + subTree.getRayTriangles( ray, triangles ); + + } + + } + + return triangles; + + }, + + triangleCapsuleIntersect: function ( capsule, triangle ) { + + var point1, point2, line1, line2; + + triangle.getPlane( _plane ); + + var d1 = _plane.distanceToPoint( capsule.start ) - capsule.radius; + var d2 = _plane.distanceToPoint( capsule.end ) - capsule.radius; + + if ( ( d1 > 0 && d2 > 0 ) || ( d1 < - capsule.radius && d2 < - capsule.radius ) ) { + + return false; + + } + + var delta = Math.abs( d1 / ( Math.abs( d1 ) + Math.abs( d2 ) ) ); + var intersectPoint = _v1.copy( capsule.start ).lerp( capsule.end, delta ); + + if ( triangle.containsPoint( intersectPoint ) ) { + + return { normal: _plane.normal.clone(), point: intersectPoint.clone(), depth: Math.abs( Math.min( d1, d2 ) ) }; + + } + + var r2 = capsule.radius * capsule.radius; + + line1 = _line1.set( capsule.start, capsule.end ); + + var lines = [ + [ triangle.a, triangle.b ], + [ triangle.b, triangle.c ], + [ triangle.c, triangle.a ] + ]; + + for ( var i = 0; i < lines.length; i ++ ) { + + line2 = _line2.set( lines[ i ][ 0 ], lines[ i ][ 1 ] ); + + [ point1, point2 ] = capsule.lineLineMinimumPoints( line1, line2 ); + + if ( point1.distanceToSquared( point2 ) < r2 ) { + + return { normal: point1.clone().sub( point2 ).normalize(), point: point2.clone(), depth: capsule.radius - point1.distanceTo( point2 ) }; + + } + + } + + return false; + + }, + + triangleSphereIntersect: function ( sphere, triangle ) { + + triangle.getPlane( _plane ); + + if ( ! sphere.intersectsPlane( _plane ) ) return false; + + var depth = Math.abs( _plane.distanceToSphere( sphere ) ); + var r2 = sphere.radius * sphere.radius - depth * depth; + + var plainPoint = _plane.projectPoint( sphere.center, _v1 ); + + if ( triangle.containsPoint( sphere.center ) ) { + + return { normal: _plane.normal.clone(), point: plainPoint.clone(), depth: Math.abs( _plane.distanceToSphere( sphere ) ) }; + + } + + var lines = [ + [ triangle.a, triangle.b ], + [ triangle.b, triangle.c ], + [ triangle.c, triangle.a ] + ]; + + for ( var i = 0; i < lines.length; i ++ ) { + + _line1.set( lines[ i ][ 0 ], lines[ i ][ 1 ] ); + _line1.closestPointToPoint( plainPoint, true, _v2 ); + + var d = _v2.distanceToSquared( sphere.center ); + + if ( d < r2 ) { + + return { normal: sphere.center.clone().sub( _v2 ).normalize(), point: _v2.clone(), depth: sphere.radius - Math.sqrt( d ) }; + + } + + } + + return false; + + }, + + getSphereTriangles: function ( sphere, triangles ) { + + for ( var i = 0; i < this.subTrees.length; i ++ ) { + + var subTree = this.subTrees[ i ]; + + if ( ! sphere.intersectsBox( subTree.box ) ) continue; + + if ( subTree.triangles.length > 0 ) { + + for ( var j = 0; j < subTree.triangles.length; j ++ ) { + + if ( triangles.indexOf( subTree.triangles[ j ] ) === - 1 ) triangles.push( subTree.triangles[ j ] ); + + } + + } else { + + subTree.getSphereTriangles( sphere, triangles ); + + } + + } + + }, + + getCapsuleTriangles: function ( capsule, triangles ) { + + for ( var i = 0; i < this.subTrees.length; i ++ ) { + + var subTree = this.subTrees[ i ]; + + if ( ! capsule.intersectsBox( subTree.box ) ) continue; + + if ( subTree.triangles.length > 0 ) { + + for ( var j = 0; j < subTree.triangles.length; j ++ ) { + + if ( triangles.indexOf( subTree.triangles[ j ] ) === - 1 ) triangles.push( subTree.triangles[ j ] ); + + } + + } else { + + subTree.getCapsuleTriangles( capsule, triangles ); + + } + + } + + }, + + sphereIntersect( sphere ) { + + _sphere.copy( sphere ); + + var triangles = [], result, hit = false; + + this.getSphereTriangles( sphere, triangles ); + + for ( var i = 0; i < triangles.length; i ++ ) { + + if ( result = this.triangleSphereIntersect( _sphere, triangles[ i ] ) ) { + + hit = true; + + _sphere.center.add( result.normal.multiplyScalar( result.depth ) ); + + } + + } + + if ( hit ) { + + var collisionVector = _sphere.center.clone().sub( sphere.center ); + var depth = collisionVector.length(); + + return { normal: collisionVector.normalize(), depth: depth }; + + } + + return false; + + }, + + capsuleIntersect: function ( capsule ) { + + _capsule.copy( capsule ); + + var triangles = [], result, hit = false; + + this.getCapsuleTriangles( _capsule, triangles ); + + for ( var i = 0; i < triangles.length; i ++ ) { + + if ( result = this.triangleCapsuleIntersect( _capsule, triangles[ i ] ) ) { + + hit = true; + + _capsule.translate( result.normal.multiplyScalar( result.depth ) ); + + } + + } + + if ( hit ) { + + var collisionVector = _capsule.getCenter( new Vector3() ).sub( capsule.getCenter( _v1 ) ); + var depth = collisionVector.length(); + + return { normal: collisionVector.normalize(), depth: depth }; + + } + + return false; + + }, + + rayIntersect: function ( ray ) { + + if ( ray.direction.length() === 0 ) return; + + var triangles = [], triangle, position, + distance = 1e100, + result; + + this.getRayTriangles( ray, triangles ); + + for ( var i = 0; i < triangles.length; i ++ ) { + + result = ray.intersectTriangle( triangles[ i ].a, triangles[ i ].b, triangles[ i ].c, true, _v1 ); + + if ( result ) { + + var newdistance = result.sub( ray.origin ).length(); + + if ( distance > newdistance ) { + + position = result.clone().add( ray.origin ); + distance = newdistance; + triangle = triangles[ i ]; + + } + + } + + } + + return distance < 1e100 ? { distance: distance, triangle: triangle, position: position } : false; + + }, + + fromGraphNode: function ( group ) { + + group.traverse( ( obj ) => { + + if ( obj.type === 'Mesh' ) { + + obj.updateMatrix(); + obj.updateWorldMatrix(); + + var geometry, isTemp = false; + + if ( obj.geometry.index ) { + + isTemp = true; + geometry = obj.geometry.clone().toNonIndexed(); + + } else { + + geometry = obj.geometry; + + } + + var positions = geometry.attributes.position.array; + var transform = obj.matrixWorld; + + for ( var i = 0; i < positions.length; i += 9 ) { + + var v1 = new Vector3( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ); + var v2 = new Vector3( positions[ i + 3 ], positions[ i + 4 ], positions[ i + 5 ] ); + var v3 = new Vector3( positions[ i + 6 ], positions[ i + 7 ], positions[ i + 8 ] ); + + v1.applyMatrix4( transform ); + v2.applyMatrix4( transform ); + v3.applyMatrix4( transform ); + + this.addTriangle( new Triangle( v1, v2, v3 ) ); + + } + + if ( isTemp ) { + + geometry.dispose(); + + } + + } + + } ); + + this.build(); + + return this; + + } + + } ); + + return Octree; + +} )(); + +export { Octree }; diff --git a/examples/misc_octree_collisions.html b/examples/misc_octree_collisions.html new file mode 100644 index 00000000000000..7e54f81b076fd2 --- /dev/null +++ b/examples/misc_octree_collisions.html @@ -0,0 +1,373 @@ + + + + three.js - misc - octree collisions + + + + + +
Octree threejs demo - basic collisions with static triangle mesh
WASD to move, space to jump, use mouse to throw balls and move the camera.
+
+ + + + \ No newline at end of file diff --git a/examples/models/gltf/collision-world.glb b/examples/models/gltf/collision-world.glb new file mode 100644 index 0000000000000000000000000000000000000000..4335cbfeb0d17d7d3eef288af9671ed7a6506242 GIT binary patch literal 94556 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 literal 0 HcmV?d00001 diff --git a/examples/screenshots/misc_octree_collisions.jpg b/examples/screenshots/misc_octree_collisions.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0a6ef920e5f91166c6e8d937e48adfa755d0f9fb GIT binary patch literal 29653 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 URYw}G=S6Xm8B!ub?cePG08%KJO#lD@ literal 0 HcmV?d00001 diff --git a/utils/modularize.js b/utils/modularize.js index 1a5935f03cb61d..c90ebcb6624d12 100644 --- a/utils/modularize.js +++ b/utils/modularize.js @@ -104,6 +104,8 @@ var files = [ { path: 'math/ImprovedNoise.js', dependencies: [], ignoreList: [] }, { path: 'math/Lut.js', dependencies: [], ignoreList: [] }, { path: 'math/SimplexNoise.js', dependencies: [], ignoreList: [] }, + { path: 'math/Capsule.js', dependencies: [], ignoreList: [] }, + { path: 'math/Octree.js', dependencies: [ { name: 'Capsule', path: 'math/Capsule.js' } ], ignoreList: [] }, { path: 'misc/ConvexObjectBreaker.js', dependencies: [ { name: 'ConvexBufferGeometry', path: 'geometries/ConvexGeometry.js' } ], ignoreList: [ 'Matrix4' ] }, { path: 'misc/GPUComputationRenderer.js', dependencies: [], ignoreList: [] },