Raycasting intersection error

Hey! I’m trying to develop a program which takes a gltf file and lets users draw 3d strokes within a shape to fill it in, and then make the resulting mesh exportable. I’m running into an issue with actually getting the stroke to fall within the mesh- I attached a picture below. Currently I’m detecting if the stroke is within the mesh by checking if at least half of projected rays intersect the mesh but I’m not sure if that’s correct. I attached the problem methods below
function getMousePosition(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}

// Check if point is inside case boundary
function isInsideCase(point) {
if (caseGeometry.length === 0) return false;

// Cast rays in multiple directions to determine if point is inside
const directions = [
new THREE.Vector3(1, 0, 0),
new THREE.Vector3(-1, 0, 0),
new THREE.Vector3(0, 1, 0),
new THREE.Vector3(0, -1, 0),
new THREE.Vector3(0, 0, 1),
new THREE.Vector3(0, 0, -1)
];

let insideCount = 0;

for (let dir of directions) {
raycaster.set(point, dir);
const intersects = raycaster.intersectObjects(caseGeometry, true);
if (intersects.length % 2 === 1) {
insideCount++;
}
}

// If majority of rays indicate inside, point is inside
return insideCount > directions.length / 2;
}

// Get intersection point with case
function getIntersectionPoint(event) {
getMousePosition(event);
raycaster.setFromCamera(mouse, camera);

// First try to intersect with case meshes
const caseIntersects = raycaster.intersectObjects(caseGeometry, true);
if (caseIntersects.length > 0) {
const point = caseIntersects[0].point;
// Offset slightly inward from the surface
const normal = caseIntersects[0].face.normal.clone();
normal.transformDirection(caseIntersects[0].object.matrixWorld);
point.add(normal.multiplyScalar(-0.02));
return point;
}

// If no case intersection, create a point at a fixed distance from camera
const direction = new THREE.Vector3();
raycaster.ray.direction.clone().normalize();
const distance = 2; // Fixed distance from camera
const point = camera.position.clone().add(raycaster.ray.direction.multiplyScalar(distance));

return point;
}

// Create tube geometry from stroke points
function createStrokeTube(points, radius = 0.2) {
if (points.length < 4) return null;

try {
const curve = new THREE.CatmullRomCurve3(points);
const tubeGeometry = new THREE.TubeGeometry(curve, points.length * 2, radius, 8, false);
const tubeMaterial = new THREE.MeshStandardMaterial({
color: 0xc0c0c0, // Silver color
metalness: 0.8,
roughness: 0.2,
});

const mesh = new THREE.Mesh(tubeGeometry, tubeMaterial);
console.log('Successfully created tube mesh'); // Debug output
return mesh;

} catch (error) {
console.error(‘Error creating tube:’, error);

// Fallback: create spheres at each point
const group = new THREE.Group();
const sphereGeometry = new THREE.SphereGeometry(radius, 8, 8);
const sphereMaterial = new THREE.MeshStandardMaterial({
  color: 0xc0c0c0,
  metalness: 0.8,
  roughness: 0.2,
});

points.forEach(point => {
  const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
  sphere.position.copy(point);
  group.add(sphere);
});

console.log('Created fallback spheres'); // Debug output
return group;

}
}

Have you considered using three-mesh-bvh intersections or a simple Box3.intersect for this?

There are a couple gotchas with raycasting.

The first is that if your renderer is not COMPLETELY FULL SCREEN, the default mouse position calculation WILL NOT WORK:

//This does not work if the renderer is not exactly the size of the window, no padding, no margin, etc.
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

Another gotcha, is that as soon as you start adding markers for what you’ve hit, those markers will start getting ray hits first, so if you’re only processing the first hit [0] from a scene raycast, and adding a marker for the hit, the next cast you do will hit your marker first and the result will be wrong.

Do you think either of these gotchas are in play?

(And.. just to reassure you.. when set up correctly.. raycasting works pretty flawlessly, so this is 99.% not some weird bug in threejs.)