Check if Point is Inside ConvexGeometry in Three.js
This article explains how to efficiently determine if a specific 3D
point lies strictly inside a complex ConvexGeometry in
Three.js. We will explore the mathematical approach of using face planes
and provide a clean, high-performance JavaScript function to perform
this check in local or world space.
The Mathematical Approach: Face Planes
Because a ConvexGeometry is convex, we can leverage a
key geometric property: a point is strictly inside a convex
polyhedron if and only if it lies on the interior side of every single
face plane.
In Three.js, the vertices of the outer shell are wound counter-clockwise. This means we can construct a plane for each triangular face where the normal vector points outward. If the signed distance from our target 3D point to every face plane is negative, the point is strictly inside the geometry.
Accounting for Object Transformations
Before running the check, you must ensure the point and the geometry are in the same coordinate space. If your mesh has been scaled, rotated, or translated, it is highly inefficient to transform the entire geometry. Instead, transform the query point from world space to the mesh’s local space using the inverse of the mesh’s world matrix.
Code Implementation
The following function performs the check. It handles both indexed
and non-indexed BufferGeometry structures and accounts for
mesh transformations.
import * as THREE from 'three';
/**
* Determines if a world-space point is strictly inside a ConvexGeometry mesh.
* @param {THREE.Vector3} worldPoint - The point to test in world coordinates.
* @param {THREE.Mesh} mesh - The Mesh containing the ConvexGeometry.
* @returns {boolean} True if the point is strictly inside, false otherwise.
*/
function isPointInsideConvexMesh(worldPoint, mesh) {
const geometry = mesh.geometry;
// 1. Convert the world point to the mesh's local coordinate space
const localPoint = worldPoint.clone().applyMatrix4(mesh.matrixWorld.invert());
const positionAttribute = geometry.attributes.position;
const index = geometry.index;
const vertexA = new THREE.Vector3();
const vertexB = new THREE.Vector3();
const vertexC = new THREE.Vector3();
const plane = new THREE.Plane();
const faceCount = index ? index.count / 3 : positionAttribute.count / 3;
// 2. Iterate through every triangular face of the geometry
for (let i = 0; i < faceCount; i++) {
if (index) {
vertexA.fromBufferAttribute(positionAttribute, index.getX(i * 3));
vertexB.fromBufferAttribute(positionAttribute, index.getY(i * 3));
vertexC.fromBufferAttribute(positionAttribute, index.getZ(i * 3));
} else {
vertexA.fromBufferAttribute(positionAttribute, i * 3);
vertexB.fromBufferAttribute(positionAttribute, i * 3 + 1);
vertexC.fromBufferAttribute(positionAttribute, i * 3 + 2);
}
// Create a plane from the face vertices (normal points outward)
plane.setFromCoplanarPoints(vertexA, vertexB, vertexC);
// Calculate the signed distance from the point to the plane
const distance = plane.distanceToPoint(localPoint);
// If distance is >= 0, the point is on or outside this face.
// For strictly inside, we use a small epsilon offset to avoid floating-point errors.
const epsilon = 0.000001;
if (distance >= -epsilon) {
return false;
}
}
// The point is on the negative (inner) side of all planes
return true;
}Performance and Complexity
- Time Complexity: \(O(F)\) where \(F\) is the number of faces in the geometry.
Since
ConvexGeometryhulls are typically simplified and contain fewer faces than standard high-poly meshes, this check is incredibly fast. - Memory Overhead: \(O(1)\) auxiliary space. The function reuses
Vector3andPlaneinstances, preventing garbage collection spikes during animation frames.