import { AABB } from '../math/AABB.js';
import { BoundingSphere } from '../math/BoundingSphere.js';
import { Vector3 } from '../math/Vector3.js';
import { Ray } from '../math/Ray';
import { Plane } from '../math/Plane';
import { Matrix4 } from '../math/Matrix4';
const boundingSphere = new BoundingSphere();
const triangle = { a: new Vector3(), b: new Vector3(), c: new Vector3() };
const rayLocal = new Ray();
const plane = new Plane();
const inverseMatrix = new Matrix4();
const closestIntersectionPoint = new Vector3();
const closestTriangle = { a: new Vector3(), b: new Vector3(), c: new Vector3() };
/**
* Class for representing a polygon mesh. The faces consist of triangles.
*
* @author {@link https://github.com/Mugen87|Mugen87}
*/
class MeshGeometry {
/**
* Constructs a new mesh geometry.
*
* @param {TypedArray} vertices - The vertex buffer (Float32Array).
* @param {TypedArray} indices - The index buffer (Uint16Array/Uint32Array).
*/
constructor( vertices = new Float32Array(), indices = null ) {
/**
* The vertex buffer.
* @type {Float32Array}
*/
this.vertices = vertices;
/**
* The index buffer.
* @type {?(Uint16Array|?Uint32Array)}
* @default null
*/
this.indices = indices;
/**
* Whether back face culling is active or not. Only relevant for raycasting.
* @type {Boolean}
*/
this.backfaceCulling = true;
/**
* An AABB enclosing the geometry.
* @type {AABB}
*/
this.aabb = new AABB();
/**
* A bounding sphere enclosing the geometry.
* @type {BoundingSphere}
*/
this.boundingSphere = new BoundingSphere();
this.computeBoundingVolume();
}
/**
* Computes the internal bounding volumes of this mesh geometry.
*
* @return {MeshGeometry} A reference to this mesh geometry.
*/
computeBoundingVolume() {
const vertices = this.vertices;
const vertex = new Vector3();
const aabb = this.aabb;
const boundingSphere = this.boundingSphere;
// compute AABB
aabb.min.set( Infinity, Infinity, Infinity );
aabb.max.set( - Infinity, - Infinity, - Infinity );
for ( let i = 0, l = vertices.length; i < l; i += 3 ) {
vertex.x = vertices[ i ];
vertex.y = vertices[ i + 1 ];
vertex.z = vertices[ i + 2 ];
aabb.expand( vertex );
}
// compute bounding sphere
aabb.getCenter( boundingSphere.center );
boundingSphere.radius = boundingSphere.center.distanceTo( aabb.max );
return this;
}
/**
* Performs a ray intersection test with the geometry of the obstacle and stores
* the intersection point in the given result vector. If no intersection is detected,
* *null* is returned.
*
* @param {Ray} ray - The ray to test.
* @param {Matrix4} worldMatrix - The matrix that transforms the geometry to world space.
* @param {Boolean} closest - Whether the closest intersection point should be computed or not.
* @param {Vector3} intersectionPoint - The intersection point.
* @param {Vector3} normal - The normal vector of the respective triangle.
* @return {Vector3} The result vector.
*/
intersectRay( ray, worldMatrix, closest, intersectionPoint, normal = null ) {
// check bounding sphere first in world space
boundingSphere.copy( this.boundingSphere ).applyMatrix4( worldMatrix );
if ( ray.intersectsBoundingSphere( boundingSphere ) ) {
// transform the ray into the local space of the obstacle
worldMatrix.getInverse( inverseMatrix );
rayLocal.copy( ray ).applyMatrix4( inverseMatrix );
// check AABB in local space since its more expensive to convert an AABB to world space than a bounding sphere
if ( rayLocal.intersectsAABB( this.aabb ) ) {
// now perform more expensive test with all triangles of the geometry
const vertices = this.vertices;
const indices = this.indices;
let minDistance = Infinity;
let found = false;
if ( indices === null ) {
// non-indexed geometry
for ( let i = 0, l = vertices.length; i < l; i += 9 ) {
triangle.a.set( vertices[ i ], vertices[ i + 1 ], vertices[ i + 2 ] );
triangle.b.set( vertices[ i + 3 ], vertices[ i + 4 ], vertices[ i + 5 ] );
triangle.c.set( vertices[ i + 6 ], vertices[ i + 7 ], vertices[ i + 8 ] );
if ( rayLocal.intersectTriangle( triangle, this.backfaceCulling, intersectionPoint ) !== null ) {
if ( closest ) {
const distance = intersectionPoint.squaredDistanceTo( rayLocal.origin );
if ( distance < minDistance ) {
minDistance = distance;
closestIntersectionPoint.copy( intersectionPoint );
closestTriangle.a.copy( triangle.a );
closestTriangle.b.copy( triangle.b );
closestTriangle.c.copy( triangle.c );
found = true;
}
} else {
found = true;
break;
}
}
}
} else {
// indexed geometry
for ( let i = 0, l = indices.length; i < l; i += 3 ) {
const a = indices[ i ];
const b = indices[ i + 1 ];
const c = indices[ i + 2 ];
const stride = 3;
triangle.a.set( vertices[ ( a * stride ) ], vertices[ ( a * stride ) + 1 ], vertices[ ( a * stride ) + 2 ] );
triangle.b.set( vertices[ ( b * stride ) ], vertices[ ( b * stride ) + 1 ], vertices[ ( b * stride ) + 2 ] );
triangle.c.set( vertices[ ( c * stride ) ], vertices[ ( c * stride ) + 1 ], vertices[ ( c * stride ) + 2 ] );
if ( rayLocal.intersectTriangle( triangle, this.backfaceCulling, intersectionPoint ) !== null ) {
if ( closest ) {
const distance = intersectionPoint.squaredDistanceTo( rayLocal.origin );
if ( distance < minDistance ) {
minDistance = distance;
closestIntersectionPoint.copy( intersectionPoint );
closestTriangle.a.copy( triangle.a );
closestTriangle.b.copy( triangle.b );
closestTriangle.c.copy( triangle.c );
found = true;
}
} else {
found = true;
break;
}
}
}
}
// intersection was found
if ( found ) {
if ( closest ) {
// restore closest intersection point and triangle
intersectionPoint.copy( closestIntersectionPoint );
triangle.a.copy( closestTriangle.a );
triangle.b.copy( closestTriangle.b );
triangle.c.copy( closestTriangle.c );
}
// transform intersection point back to world space
intersectionPoint.applyMatrix4( worldMatrix );
// compute normal of triangle in world space if necessary
if ( normal !== null ) {
plane.fromCoplanarPoints( triangle.a, triangle.b, triangle.c );
normal.copy( plane.normal );
normal.transformDirection( worldMatrix );
}
return intersectionPoint;
}
}
}
return null;
}
/**
* Returns a new geometry without containing indices. If the geometry is already
* non-indexed, the method performs no changes.
*
* @return {MeshGeometry} The new non-indexed geometry.
*/
toTriangleSoup() {
const indices = this.indices;
if ( indices ) {
const vertices = this.vertices;
const newVertices = new Float32Array( indices.length * 3 );
for ( let i = 0, l = indices.length; i < l; i ++ ) {
const a = indices[ i ];
const stride = 3;
newVertices[ i * stride ] = vertices[ a * stride ];
newVertices[ ( i * stride ) + 1 ] = vertices[ ( a * stride ) + 1 ];
newVertices[ ( i * stride ) + 2 ] = vertices[ ( a * stride ) + 2 ];
}
return new MeshGeometry( newVertices );
} else {
return this;
}
}
/**
* Transforms this instance into a JSON object.
*
* @return {Object} The JSON object.
*/
toJSON() {
const json = {
type: this.constructor.name
};
json.indices = {
type: this.indices ? this.indices.constructor.name : 'null',
data: this.indices ? Array.from( this.indices ) : null
};
json.vertices = Array.from( this.vertices );
json.backfaceCulling = this.backfaceCulling;
json.aabb = this.aabb.toJSON();
json.boundingSphere = this.boundingSphere.toJSON();
return json;
}
/**
* Restores this instance from the given JSON object.
*
* @param {Object} json - The JSON object.
* @return {MeshGeometry} A reference to this mesh geometry.
*/
fromJSON( json ) {
this.aabb = new AABB().fromJSON( json.aabb );
this.boundingSphere = new BoundingSphere().fromJSON( json.boundingSphere );
this.backfaceCulling = json.backfaceCulling;
this.vertices = new Float32Array( json.vertices );
switch ( json.indices.type ) {
case 'Uint16Array':
this.indices = new Uint16Array( json.indices.data );
break;
case 'Uint32Array':
this.indices = new Uint32Array( json.indices.data );
break;
case 'null':
this.indices = null;
break;
}
return this;
}
}
export { MeshGeometry };