Hello everyone,
First of all, a big thank you to the Three.js support team and the community for all the amazing work and help!
I’m currently working on drawing a polygon using Line2
from three.js
(via three/examples/jsm/lines/Line2
) to render thicker lines. However, I’m encountering an issue:
- When I start drawing, the first line segment is visible, but any subsequent lines do not render.
- The points array is updating correctly, and I am using
setPositions()
to updateLineGeometry
, but still, the additional lines don’t appear.
Here’s what I have checked so far:
- Ensured that
setPositions()
is updating correctly. - Verified that
computeLineDistances()
is being called. - Tried disposing and re-creating
LineGeometry
after updates. - Confirmed that
LineMaterial
has a resolution set (material.resolution.set(window.innerWidth, window.innerHeight)
).
Despite these checks, I’m still facing this issue. Has anyone encountered a similar problem? Any insights would be greatly appreciated!
import React, { useCallback, useEffect, useRef, useState } from 'react';
import * as THREE from 'three';
import { useFrame, useThree } from '@react-three/fiber';
import useThreeStore from '@store/threeStore';
import { getDistanceFromVector3, getPointFromMouseEvent, projectPointOntoPlane, generatePrismCutShape, movePointsAwayFromPlane } from '@utils/canvas/3d/helpers';
import { Line2 } from 'three/examples/jsm/lines/Line2';
import { CutStep, ThreeWallType } from 'src/typings/types';
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial';
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry';
interface CutItemProps { }
const CutItem: React.FC<CutItemProps> = () => {
const { cutStep, cameraControls, setCutStep, setIsDragging, addCutShape, cutShapes } = useThreeStore();
const { selectedItem } = useThreeStore();
const { gl, camera, raycaster, scene } = useThree();
const drawingStartPoint = useRef<THREE.Vector3>(new THREE.Vector3());
const drawingEndPoint = useRef<THREE.Vector3>(new THREE.Vector3());
const points = useRef<THREE.Vector3[]>([]);
const [line] = useState<Line2>(
new Line2(
new LineGeometry(),
new LineMaterial({
color: 0x00bbff,
linewidth: 5, // Line width in world units
dashed: false,
// worldUnits: true,
alphaToCoverage: false,
resolution: new THREE.Vector2(window.innerWidth, window.innerHeight),
})
));
const wallThickness = useRef<number>(1);
const substraction = useRef<number>(0.01);
const updateLineGeometry = useCallback(() => {
if (!line || points.current.length < 1) {
return;
}
const positions = points.current.flatMap(p => [p.x, p.y, p.z]);
console.log(points.current.length);
line.geometry.setDrawRange(0, positions.length * 2 + 1);
line.geometry.setPositions(positions);
line.geometry.computeBoundingBox();
line.geometry.computeBoundingSphere();
line.updateMorphTargets();
line.updateMatrixWorld();
line.updateMatrix();
line.computeLineDistances();
line.material.resolution.set(window.innerWidth, window.innerHeight); // Add this line to update material resolution
}, [line]);
const completeDrawing = useCallback((faceNormal: THREE.Vector3) => {
setCutStep(CutStep.TRANSFORM);
if (cameraControls) cameraControls.enabled = true;
points.current = movePointsAwayFromPlane(points.current.slice(0, points.current.length - 1), -wallThickness.current);
const shape = generatePrismCutShape(points.current.slice(0, points.current.length - 1), wallThickness.current);
addCutShape(shape);
points.current = [];
line.geometry.dispose();
updateLineGeometry();
setIsDragging(false);
}, [setCutStep, cameraControls, addCutShape, setIsDragging, line, updateLineGeometry]);
const onKeyUp = useCallback(
(event: KeyboardEvent) => {
if (event.key === 'Escape' && cutStep === CutStep.DRAWING) {
// Reset drawing state
points.current = [];
setIsDragging(false);
updateLineGeometry();
line.geometry.dispose();
setCutStep(CutStep.NONE);
if (cameraControls) cameraControls.enabled = true;
}
},
[cutStep, setCutStep, cameraControls, setIsDragging, line, updateLineGeometry]
);
const onMouseDown = useCallback(
(event: MouseEvent) => {
if (!selectedItem) return;
raycaster.setFromCamera(getPointFromMouseEvent(event, gl), camera);
const intersect = raycaster.intersectObject(selectedItem, true)[0];
if (!intersect) return;
const faceNormal = intersect.face?.normal
.clone()
.applyMatrix3(new THREE.Matrix3().getNormalMatrix(intersect.object.matrixWorld))
.normalize();
if (!faceNormal) return;
if (cameraControls && cutStep === CutStep.START) {
setIsDragging(true);
setCutStep(CutStep.DRAWING);
const moveDistance = 5 * (selectedItem.userData.wallType === ThreeWallType.INNER ? 1 : -1); // Distance from the face
const originPos = new THREE.Vector3();
const newCameraPos = new THREE.Vector3().addVectors(intersect.point, faceNormal.multiplyScalar(moveDistance));
cameraControls.getPosition(originPos);
const animateCamera = (fromPosition: THREE.Vector3, toPosition: THREE.Vector3, target: THREE.Vector3) => {
if (!cameraControls) return;
const duration = 1000;
const startTime = performance.now();
const animate = (time: number) => {
const elapsed = time - startTime;
const t = elapsed / duration;
if (t < 1) {
const x = fromPosition.x + t * (toPosition.x - fromPosition.x);
const y = fromPosition.y + t * (toPosition.y - fromPosition.y);
const z = fromPosition.z + t * (toPosition.z - fromPosition.z);
cameraControls.setPosition(x, y, z);
cameraControls.setTarget(target.x, target.y, target.z);
requestAnimationFrame(animate);
} else {
cameraControls.setPosition(toPosition.x, toPosition.y, toPosition.z);
cameraControls.setTarget(target.x, target.y, target.z);
cameraControls.enabled = false;
}
};
requestAnimationFrame(animate);
};
animateCamera(originPos, newCameraPos, intersect.point);
}
if (cutStep === CutStep.DRAWING) {
let p = drawingEndPoint.current;
if (points.current.length === 0) {
p = new THREE.Vector3().addVectors(intersect.point, faceNormal.multiplyScalar(substraction.current * (selectedItem.userData.wallType === ThreeWallType.INNER ? 1 : -1)));
drawingStartPoint.current = p;
}
points.current.push(p);
if (points.current.length > 3 && points.current[0].distanceTo(points.current[points.current.length - 1]) === 0) {
completeDrawing(faceNormal);
}
}
if (cutStep === CutStep.NONE) {
points.current = [];
line.geometry.dispose();
}
updateLineGeometry();
},
[camera, gl, raycaster, cameraControls, cutStep, setCutStep, selectedItem, setIsDragging, completeDrawing, updateLineGeometry, line]
);
const onMouseMove = useCallback(
(event: MouseEvent) => {
if (cutStep === CutStep.DRAWING) { // Add lineDraging.current check
if (!selectedItem) return;
raycaster.setFromCamera(getPointFromMouseEvent(event, gl), camera);
const intersect = raycaster.intersectObject(selectedItem, true)[0];
if (!intersect) return;
if (points.current.length === 0) return;
const faceNormal = intersect.face?.normal
.clone()
.applyMatrix3(new THREE.Matrix3().getNormalMatrix(intersect.object.matrixWorld))
.normalize();
if (!faceNormal) return;
const p = new THREE.Vector3().addVectors(intersect.point, faceNormal.multiplyScalar(substraction.current * (selectedItem.userData.wallType === ThreeWallType.INNER ? 1 : -1)));
drawingEndPoint.current = projectPointOntoPlane(p, intersect.point, drawingStartPoint.current, p);
if (getDistanceFromVector3(drawingStartPoint.current, drawingEndPoint.current) < 0.5) {
drawingEndPoint.current = drawingStartPoint.current;
}
points.current = points.current.length === 1 ? points.current : points.current.slice(0, points.current.length - 1);
points.current.push(drawingEndPoint.current);
updateLineGeometry();
}
},
[cutStep, selectedItem, raycaster, gl, camera, updateLineGeometry]
);
useEffect(() => {
gl.domElement.addEventListener('mousedown', onMouseDown);
gl.domElement.addEventListener('mousemove', onMouseMove);
window.addEventListener('keyup', onKeyUp); // Add keyup listener
return () => {
gl.domElement.removeEventListener('mousedown', onMouseDown);
gl.domElement.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('keyup', onKeyUp); // Remove keyup listener
};
}, [gl, onMouseDown, onMouseMove, onKeyUp]);
useFrame(() => {
if (line && points.current.length > 1 && cutStep === CutStep.DRAWING) {
line.computeLineDistances();
}
});
return (
<>
{cutStep === CutStep.DRAWING && (
<primitive object={line} />
)}
</>
);
};
export default CutItem;