Commit b40b18b8 authored by Stoll's avatar Stoll
Browse files

Placement works

parent 899f3a9c
......@@ -16,7 +16,7 @@ export const XRRenderer: React.FC = () => {
useEffect(() => {
if (!mountRef.current || !overlayRef.current) return;
// Basic scene setup
// --- Scene setup ---
const scene = new THREE.Scene();
scene.background = null;
......@@ -30,46 +30,94 @@ export const XRRenderer: React.FC = () => {
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.xr.enabled = true;
renderer.xr.setReferenceSpaceType("local-floor");
renderer.xr.setReferenceSpaceType("local");
mountRef.current.appendChild(renderer.domElement);
// --- AR button ---
const button = XRButton.createButton(renderer, {
requiredFeatures: ["hit-test", "dom-overlay"],
optionalFeatures: ["anchors"],
domOverlay: { root: overlayRef.current },
});
mountRef.current.appendChild(button);
// Lighting
// --- Lighting ---
const light = new THREE.HemisphereLight(0xffffff, 0xbbbbff, 1);
scene.add(light);
// Block manager
// --- Block manager ---
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 localRefSpace: XRReferenceSpace | null = null;
let lastHitPose: THREE.Vector3 | null = null;
let latestHitPose: Float32Array | null = null;
// --- Session start ---
const sessionStartHandler = async () => {
setIsSessionActive(true);
const session = renderer.xr.getSession();
localRefSpace = await session.requestReferenceSpace("local-floor");
localRefSpace = await session.requestReferenceSpace("local");
const viewerSpace = await session.requestReferenceSpace("viewer");
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 = () => {
setIsSessionActive(false);
hitTestSource = null;
localRefSpace = null;
lastHitPose = null;
latestHitPose = null;
reticle.visible = false;
};
renderer.xr.addEventListener("sessionstart", sessionStartHandler);
renderer.xr.addEventListener("sessionend", sessionEndHandler);
// Resize handling
// --- Resize handling ---
const handleResize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
......@@ -77,52 +125,38 @@ export const XRRenderer: React.FC = () => {
};
window.addEventListener("resize", handleResize);
// Animation loop
// --- Animation loop ---
renderer.setAnimationLoop((timestamp, frame) => {
if (frame && hitTestSource && localRefSpace) {
const hitTestResults = frame.getHitTestResults(hitTestSource);
if (hitTestResults.length > 0) {
const hit = hitTestResults[0];
const pose = hit.getPose(localRefSpace);
if (pose) {
// Save the latest hit pose (for new object placement)
lastHitPose = new THREE.Vector3(
pose.transform.position.x,
pose.transform.position.y,
pose.transform.position.z
const mat = new THREE.Matrix4().fromArray(pose.transform.matrix);
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.y,
pose.transform.position.z,
]);
}
} else {
reticle.visible = false;
}
}
renderer.render(scene, camera);
});
// === Add object at the hit-test location when selected ===
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
// --- Cleanup ---
return () => {
renderer.xr.removeEventListener("sessionstart", sessionStartHandler);
renderer.xr.removeEventListener("sessionend", sessionEndHandler);
......@@ -132,12 +166,7 @@ export const XRRenderer: React.FC = () => {
};
}, [selectedObject]);
// --- Handle object placement from bottom bar ---
const handleSelect = (name: string) => {
setSelectedObject(name);
// Trigger placement
(window as any).addSelectedObject?.();
};
const handleSelect = (name: string) => setSelectedObject(name);
return (
<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