Commit b40b18b8 authored by Stoll's avatar Stoll
Browse files

Placement works

parent 899f3a9c
...@@ -16,7 +16,7 @@ export const XRRenderer: React.FC = () => { ...@@ -16,7 +16,7 @@ export const XRRenderer: React.FC = () => {
useEffect(() => { useEffect(() => {
if (!mountRef.current || !overlayRef.current) return; if (!mountRef.current || !overlayRef.current) return;
// Basic scene setup // --- Scene setup ---
const scene = new THREE.Scene(); const scene = new THREE.Scene();
scene.background = null; scene.background = null;
...@@ -30,46 +30,94 @@ export const XRRenderer: React.FC = () => { ...@@ -30,46 +30,94 @@ export const XRRenderer: React.FC = () => {
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight); renderer.setSize(window.innerWidth, window.innerHeight);
renderer.xr.enabled = true; renderer.xr.enabled = true;
renderer.xr.setReferenceSpaceType("local-floor"); renderer.xr.setReferenceSpaceType("local");
mountRef.current.appendChild(renderer.domElement); mountRef.current.appendChild(renderer.domElement);
// --- AR button ---
const button = XRButton.createButton(renderer, { const button = XRButton.createButton(renderer, {
requiredFeatures: ["hit-test", "dom-overlay"], requiredFeatures: ["hit-test", "dom-overlay"],
optionalFeatures: ["anchors"],
domOverlay: { root: overlayRef.current }, domOverlay: { root: overlayRef.current },
}); });
mountRef.current.appendChild(button); mountRef.current.appendChild(button);
// Lighting // --- Lighting ---
const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 1); const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 1);
scene.add(light); scene.add(light);
// Block manager // --- Block manager ---
const blockManager = new BlockManager(scene); const blockManager = new BlockManager(scene);
// Track hit-test info // --- Reticle for placement ---
const reticle = new THREE.Mesh(
new THREE.RingGeometry(0.08, 0.1, 32).rotateX(-Math.PI / 2),
new THREE.MeshBasicMaterial({ color: 0x00ff00 })
);
reticle.visible = false;
scene.add(reticle);
// --- Hit-test variables ---
let hitTestSource: XRHitTestSource | null = null; let hitTestSource: XRHitTestSource | null = null;
let localRefSpace: XRReferenceSpace | null = null; let localRefSpace: XRReferenceSpace | null = null;
let lastHitPose: THREE.Vector3 | null = null; let latestHitPose: Float32Array | null = null;
// --- Session start ---
const sessionStartHandler = async () => { const sessionStartHandler = async () => {
setIsSessionActive(true); setIsSessionActive(true);
const session = renderer.xr.getSession(); const session = renderer.xr.getSession();
localRefSpace = await session.requestReferenceSpace("local-floor"); localRefSpace = await session.requestReferenceSpace("local");
const viewerSpace = await session.requestReferenceSpace("viewer"); const viewerSpace = await session.requestReferenceSpace("viewer");
hitTestSource = await session.requestHitTestSource({ space: viewerSpace }); hitTestSource = await session.requestHitTestSource({ space: viewerSpace });
// --- Tap to place ---
session.addEventListener("select", async () => {
if (!latestHitPose) return;
const position = new THREE.Vector3(
latestHitPose[0],
latestHitPose[1],
latestHitPose[2]
);
let objectToAdd;
if (selectedObject === "Cube") {
objectToAdd = new Cube(position);
} else if (selectedObject === "Watermill") {
objectToAdd = new GLTFModel("/watermill.glb", position);
} else if (selectedObject === "Tree") {
objectToAdd = new GLTFModel("/tree.glb", position);
} else {
objectToAdd = new Cube(position);
}
blockManager.addObject(objectToAdd);
// --- Optionally create an anchor so it stays fixed in the world ---
if (session.requestAnchor && position) {
const anchorPose = new XRRigidTransform(
{ x: position.x, y: position.y, z: position.z }
);
try {
await session.requestAnchor(anchorPose, localRefSpace);
} catch (e) {
console.warn("Anchors not supported:", e);
}
}
});
}; };
const sessionEndHandler = () => { const sessionEndHandler = () => {
setIsSessionActive(false); setIsSessionActive(false);
hitTestSource = null; hitTestSource = null;
localRefSpace = null; localRefSpace = null;
lastHitPose = null; latestHitPose = null;
reticle.visible = false;
}; };
renderer.xr.addEventListener("sessionstart", sessionStartHandler); renderer.xr.addEventListener("sessionstart", sessionStartHandler);
renderer.xr.addEventListener("sessionend", sessionEndHandler); renderer.xr.addEventListener("sessionend", sessionEndHandler);
// Resize handling // --- Resize handling ---
const handleResize = () => { const handleResize = () => {
camera.aspect = window.innerWidth / window.innerHeight; camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix(); camera.updateProjectionMatrix();
...@@ -77,52 +125,38 @@ export const XRRenderer: React.FC = () => { ...@@ -77,52 +125,38 @@ export const XRRenderer: React.FC = () => {
}; };
window.addEventListener("resize", handleResize); window.addEventListener("resize", handleResize);
// Animation loop // --- Animation loop ---
renderer.setAnimationLoop((timestamp, frame) => { renderer.setAnimationLoop((timestamp, frame) => {
if (frame && hitTestSource && localRefSpace) { if (frame && hitTestSource && localRefSpace) {
const hitTestResults = frame.getHitTestResults(hitTestSource); const hitTestResults = frame.getHitTestResults(hitTestSource);
if (hitTestResults.length > 0) { if (hitTestResults.length > 0) {
const hit = hitTestResults[0]; const hit = hitTestResults[0];
const pose = hit.getPose(localRefSpace); const pose = hit.getPose(localRefSpace);
if (pose) { if (pose) {
// Save the latest hit pose (for new object placement) const mat = new THREE.Matrix4().fromArray(pose.transform.matrix);
lastHitPose = new THREE.Vector3( reticle.visible = true;
reticle.matrix.fromArray(pose.transform.matrix);
reticle.matrix.decompose(
reticle.position,
reticle.quaternion,
reticle.scale
);
latestHitPose = new Float32Array([
pose.transform.position.x, pose.transform.position.x,
pose.transform.position.y, pose.transform.position.y,
pose.transform.position.z pose.transform.position.z,
); ]);
} }
} else {
reticle.visible = false;
} }
} }
renderer.render(scene, camera); renderer.render(scene, camera);
}); });
// === Add object at the hit-test location when selected === // --- Cleanup ---
const addSelectedObject = () => {
if (!lastHitPose) {
console.warn("No surface detected yet. Move camera around to find a plane.");
return;
}
let objectToAdd;
if (selectedObject === "Cube") {
objectToAdd = new Cube(lastHitPose);
} else if (selectedObject === "Watermill") {
objectToAdd = new GLTFModel("/watermill.glb", lastHitPose);
} else if (selectedObject === "Tree") {
objectToAdd = new GLTFModel("/tree.glb", lastHitPose);
} else {
objectToAdd = new Cube(lastHitPose);
}
blockManager.addObject(objectToAdd);
};
// Expose function to the window for debugging (optional)
(window as any).addSelectedObject = addSelectedObject;
// Cleanup
return () => { return () => {
renderer.xr.removeEventListener("sessionstart", sessionStartHandler); renderer.xr.removeEventListener("sessionstart", sessionStartHandler);
renderer.xr.removeEventListener("sessionend", sessionEndHandler); renderer.xr.removeEventListener("sessionend", sessionEndHandler);
...@@ -132,12 +166,7 @@ export const XRRenderer: React.FC = () => { ...@@ -132,12 +166,7 @@ export const XRRenderer: React.FC = () => {
}; };
}, [selectedObject]); }, [selectedObject]);
// --- Handle object placement from bottom bar --- const handleSelect = (name: string) => setSelectedObject(name);
const handleSelect = (name: string) => {
setSelectedObject(name);
// Trigger placement
(window as any).addSelectedObject?.();
};
return ( return (
<div style={{ width: "100vw", height: "100vh", position: "relative" }}> <div style={{ width: "100vw", height: "100vh", position: "relative" }}>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment