From b4bb3db45f8b805411b462745df6770ef38cc54d Mon Sep 17 00:00:00 2001 From: Angela Busato Date: Wed, 7 Dec 2022 11:00:05 +0100 Subject: [PATCH 1/4] feat: add vr button --- index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.js b/index.js index 208694d..a1d5778 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ import * as THREE from "https://threejsfundamentals.org/threejs/resources/threejs/r127/build/three.module.js"; import { TrackballControls } from "https://threejsfundamentals.org/threejs/resources/threejs/r127/examples/jsm/controls/TrackballControls.js"; +import { VRButton } from "https://threejsfundamentals.org/threejs/resources/threejs/r132/examples/jsm/webxr/VRButton.js"; let renderer, scene, planeMesh, planeMesh2, group; @@ -13,6 +14,8 @@ function init() { renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); renderer.localClippingEnabled = true; + renderer.xr.enabled = true; + document.body.appendChild(VRButton.createButton(renderer)); let container = document.createElement("div"); document.body.appendChild(container); From bb9de60801ec265949279a444a4f5907c4013afb Mon Sep 17 00:00:00 2001 From: Angela Busato Date: Wed, 7 Dec 2022 13:58:34 +0100 Subject: [PATCH 2/4] feat: add vr controls --- index.js | 328 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 264 insertions(+), 64 deletions(-) diff --git a/index.js b/index.js index a1d5778..396ee92 100644 --- a/index.js +++ b/index.js @@ -1,117 +1,272 @@ import * as THREE from "https://threejsfundamentals.org/threejs/resources/threejs/r127/build/three.module.js"; - -import { TrackballControls } from "https://threejsfundamentals.org/threejs/resources/threejs/r127/examples/jsm/controls/TrackballControls.js"; import { VRButton } from "https://threejsfundamentals.org/threejs/resources/threejs/r132/examples/jsm/webxr/VRButton.js"; +import { OrbitControls } from "https://threejsfundamentals.org/threejs/resources/threejs/r132/examples/jsm/controls/OrbitControls.js"; +import { XRControllerModelFactory } from "https://threejsfundamentals.org/threejs/resources/threejs/r132/examples/jsm/webxr/XRControllerModelFactory.js"; + +let container; +let camera, scene, renderer; +let controller1, controller2; +let controllerGrip1, controllerGrip2; + +let raycaster; + +const intersected = []; +const tempMatrix = new THREE.Matrix4(); + +let controls, group; -let renderer, scene, planeMesh, planeMesh2, group; +let planeMesh, planeMesh2; let planes = []; let planesOriginal = []; -function init() { - renderer = new THREE.WebGLRenderer({ antialias: true }); - renderer.setClearColor(0xf0f0f0); - renderer.setPixelRatio(window.devicePixelRatio); - renderer.setSize(window.innerWidth, window.innerHeight); - renderer.localClippingEnabled = true; - renderer.xr.enabled = true; - document.body.appendChild(VRButton.createButton(renderer)); +init(); +animate(); - let container = document.createElement("div"); +function init() { + container = document.createElement("div"); document.body.appendChild(container); - container.appendChild(renderer.domElement); - window.addEventListener("resize", onWindowResize, false); - - let camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000); - camera.position.x = 0; - camera.position.y = -200; - camera.position.z = 100; - - let controls = new TrackballControls(camera, container); - controls.rotateSpeed = 10.0; scene = new THREE.Scene(); - scene.add(new THREE.AmbientLight(0x505050)); - - // LIGHTS - const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // color, intensity - scene.add(ambientLight); + scene.background = new THREE.Color(0x808080); + + camera = new THREE.PerspectiveCamera( + 70, + window.innerWidth / window.innerHeight, + 0.1, + 10 + ); + camera.position.set(0, 1.6, 3); + + controls = new OrbitControls(camera, container); + controls.target.set(0, 1.6, 0); + controls.update(); + + const floorGeometry = new THREE.PlaneGeometry(4, 4); + const floorMaterial = new THREE.MeshStandardMaterial({ + color: 0xeeeeee, + roughness: 1.0, + metalness: 0.0, + }); + const floor = new THREE.Mesh(floorGeometry, floorMaterial); + floor.rotation.x = -Math.PI / 2; + floor.receiveShadow = true; + scene.add(floor); + + scene.add(new THREE.HemisphereLight(0x808080, 0x606060)); + + const light = new THREE.DirectionalLight(0xffffff); + light.position.set(0, 6, 0); + light.castShadow = true; + light.shadow.camera.top = 2; + light.shadow.camera.bottom = -2; + light.shadow.camera.right = 2; + light.shadow.camera.left = -2; + light.shadow.mapSize.set(4096, 4096); + scene.add(light); - const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); - directionalLight.position.copy(camera.position); - directionalLight.castShadow = true; - scene.add(directionalLight); + group = new THREE.Group(); + scene.add(group); // Create box1 - const geometry = new THREE.BoxGeometry(100, 100, 100); + const geometry1 = new THREE.BoxGeometry(0.5, 0.5, 0.5); const material = new THREE.MeshStandardMaterial({ color: "#ff0000", side: THREE.DoubleSide, }); - const box1 = new THREE.Mesh(geometry, material); + const box1 = new THREE.Mesh(geometry1, material); box1.name = "box1"; + box1.position.set(0, 1, 0); box1.renderOrder = 6; // Create box2 - const geometry2 = new THREE.BoxGeometry(70, 70, 70); + const geometry2 = new THREE.BoxGeometry(0.2, 0.2, 0.2); const material2 = new THREE.MeshStandardMaterial({ color: "#C7AC96", side: THREE.DoubleSide, }); const box2 = new THREE.Mesh(geometry2, material2); - //box2.position.set(100, 0, 0); + box2.position.set(0, 1, 0); box2.name = "box2"; box2.renderOrder = 6; - group = new THREE.Group(); group.name = "group"; group.add(box1); group.add(box2); - scene.add(group); // Create plane - const planeGeometry = new THREE.PlaneGeometry(200, 200, 1, 1); + const planeGeometry = new THREE.PlaneGeometry(1, 1, 1, 1); const planeMaterial = new THREE.MeshStandardMaterial({ color: "#38382f", side: THREE.DoubleSide, }); planeMesh = new THREE.Mesh(planeGeometry, planeMaterial); - //planeMesh.position.set(50, 0, 0); + planeMesh.position.set(0, 1, 0); planeMesh.rotation.x = Math.PI / 2; planeMesh.name = "plane"; scene.add(planeMesh); // Create plane2 - const planeGeometry2 = new THREE.PlaneGeometry(200, 200, 1, 1); + const planeGeometry2 = new THREE.PlaneGeometry(1, 1, 1, 1); const planeMaterial2 = new THREE.MeshStandardMaterial({ color: "#f0f0f0", side: THREE.DoubleSide, }); planeMesh2 = new THREE.Mesh(planeGeometry2, planeMaterial2); - //planeMesh2.position.set(50, 0, 20); + planeMesh2.position.set(0, 1, 0); planeMesh2.name = "plane2"; scene.add(planeMesh2); - function onWindowResize() { - camera.aspect = window.innerWidth / window.innerHeight; - camera.updateProjectionMatrix(); - renderer.setSize(window.innerWidth, window.innerHeight); + // Renderer + + renderer = new THREE.WebGLRenderer({ antialias: true }); + renderer.setPixelRatio(window.devicePixelRatio); + renderer.setSize(window.innerWidth, window.innerHeight); + renderer.outputEncoding = THREE.sRGBEncoding; + renderer.shadowMap.enabled = true; + renderer.localClippingEnabled = true; + renderer.xr.enabled = true; + container.appendChild(renderer.domElement); + + document.body.appendChild(VRButton.createButton(renderer)); + + // controllers + + controller1 = renderer.xr.getController(0); + controller1.addEventListener("selectstart", onSelectStart); + controller1.addEventListener("selectend", onSelectEnd); + scene.add(controller1); + + controller2 = renderer.xr.getController(1); + controller2.addEventListener("selectstart", onSelectStart); + controller2.addEventListener("selectend", onSelectEnd); + scene.add(controller2); + + const controllerModelFactory = new XRControllerModelFactory(); + + controllerGrip1 = renderer.xr.getControllerGrip(0); + controllerGrip1.add( + controllerModelFactory.createControllerModel(controllerGrip1) + ); + scene.add(controllerGrip1); + + controllerGrip2 = renderer.xr.getControllerGrip(1); + controllerGrip2.add( + controllerModelFactory.createControllerModel(controllerGrip2) + ); + scene.add(controllerGrip2); + + // + + const geometry = new THREE.BufferGeometry().setFromPoints([ + new THREE.Vector3(0, 0, 0), + new THREE.Vector3(0, 0, -1), + ]); + + const line = new THREE.Line(geometry); + line.name = "line"; + line.scale.z = 5; + + controller1.add(line.clone()); + controller2.add(line.clone()); + + raycaster = new THREE.Raycaster(); + + // + + window.addEventListener("resize", onWindowResize); +} + +function onWindowResize() { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + + renderer.setSize(window.innerWidth, window.innerHeight); +} + +function onSelectStart(event) { + const controller = event.target; + + const intersections = getIntersections(controller); + + if (intersections.length > 0) { + const intersection = intersections[0]; + + const object = intersection.object; + object.material.emissive.b = 1; + controller.attach(object); + + controller.userData.selected = object; + } +} + +function onSelectEnd(event) { + const controller = event.target; + + if (controller.userData.selected !== undefined) { + const object = controller.userData.selected; + object.material.emissive.b = 0; + group.attach(object); + + controller.userData.selected = undefined; } +} + +function getIntersections(controller) { + tempMatrix.identity().extractRotation(controller.matrixWorld); + + raycaster.ray.origin.setFromMatrixPosition(controller.matrixWorld); + raycaster.ray.direction.set(0, 0, -1).applyMatrix4(tempMatrix); + + return raycaster.intersectObjects(group.children, false); +} + +function intersectObjects(controller) { + // Do not highlight when already selected + + if (controller.userData.selected !== undefined) return; + + const line = controller.getObjectByName("line"); + const intersections = getIntersections(controller); + + if (intersections.length > 0) { + const intersection = intersections[0]; + + const object = intersection.object; + object.material.emissive.r = 1; + intersected.push(object); - function render() { - requestAnimationFrame(render); - controls.update(); - renderer.render(scene, camera); + line.scale.z = intersection.distance; + } else { + line.scale.z = 5; } +} - render(); +function cleanIntersected() { + while (intersected.length) { + const object = intersected.pop(); + object.material.emissive.r = 0; + } } -init(); +// + +function animate() { + renderer.setAnimationLoop(render); +} + +function render() { + cleanIntersected(); + + intersectObjects(controller1); + intersectObjects(controller2); + + renderer.render(scene, camera); +} const negated = document.getElementById("negated"); const negatedBox = document.getElementById("negatedBox"); @@ -119,11 +274,15 @@ const negatedBox = document.getElementById("negatedBox"); document.getElementById("clipping").addEventListener("click", () => { planes = []; planesOriginal = []; - const result = scene.children.filter((object) => object.name.startsWith("Clipping")); + const result = scene.children.filter((object) => + object.name.startsWith("Clipping") + ); if (result.length === 0) { negatedBox.style.display = "unset"; - const planesGeometry = scene.children.filter((object) => object.name.startsWith("plane")); + const planesGeometry = scene.children.filter((object) => + object.name.startsWith("plane") + ); const normals = []; const centers = []; @@ -148,9 +307,12 @@ document.getElementById("clipping").addEventListener("click", () => { }); // Calculates the barycenter of the planes - const pointx = centers.reduce((prev, curr) => prev + curr.x, 0) / centers.length; - const pointy = centers.reduce((prev, curr) => prev + curr.y, 0) / centers.length; - const pointz = centers.reduce((prev, curr) => prev + curr.z, 0) / centers.length; + const pointx = + centers.reduce((prev, curr) => prev + curr.x, 0) / centers.length; + const pointy = + centers.reduce((prev, curr) => prev + curr.y, 0) / centers.length; + const pointz = + centers.reduce((prev, curr) => prev + curr.z, 0) / centers.length; const barycenter = new THREE.Vector3(pointx, pointy, pointz); const distances = []; @@ -191,7 +353,9 @@ document.getElementById("clipping").addEventListener("click", () => { }); document.getElementById("hidePlane").addEventListener("click", () => { - const planesGeometry = scene.children.filter((object) => object.name.startsWith("plane")); + const planesGeometry = scene.children.filter((object) => + object.name.startsWith("plane") + ); planesGeometry.forEach((item) => (item.visible = !item.visible)); }); @@ -201,7 +365,9 @@ let count = 0; negated.addEventListener("click", () => { count++; - const result = scene.children.filter((object) => object.name.startsWith("Clipping")); + const result = scene.children.filter((object) => + object.name.startsWith("Clipping") + ); if (result.length > 0) { // removes the previous clipping object @@ -240,7 +406,13 @@ negated.addEventListener("click", () => { * @param {Number} renderOrder The render order of the mesh * @returns THREE.Group of meshes */ -export const createPlaneStencilGroup = (name, position, geometry, plane, renderOrder) => { +export const createPlaneStencilGroup = ( + name, + position, + geometry, + plane, + renderOrder +) => { const group = new THREE.Group(); const baseMat = new THREE.MeshBasicMaterial(); baseMat.depthWrite = false; @@ -291,7 +463,13 @@ export const createPlaneStencilGroup = (name, position, geometry, plane, renderO * @param {THREE.Plane} planesNegated The list of the negated planes * @param {THREE.Plane} planes The list of the planes */ -export const addColorToClippedMesh = (scene, group, planesNegated, planes, negatedClick) => { +export const addColorToClippedMesh = ( + scene, + group, + planesNegated, + planes, + negatedClick +) => { let object = new THREE.Group(); object.name = "ClippingGroup"; scene.add(object); @@ -301,16 +479,32 @@ export const addColorToClippedMesh = (scene, group, planesNegated, planes, negat group.children.map((mesh) => { for (let i = 0; i < planesNegated.length; i++) { const planeObj = planesNegated[i]; - const stencilGroup = createPlaneStencilGroup(mesh.name, mesh.position, mesh.geometry, planeObj, y); + const stencilGroup = createPlaneStencilGroup( + mesh.name, + mesh.position, + mesh.geometry, + planeObj, + y + ); object.add(stencilGroup); - const cap = createPlaneColored(planes, planeObj, mesh.material.color, y + 0.1, negatedClick); + const cap = createPlaneColored( + planes, + planeObj, + mesh.material.color, + y + 0.1, + negatedClick + ); cap.name = "Clipping" + mesh.name; scene.add(cap); planeObj.coplanarPoint(cap.position); - cap.lookAt(cap.position.x - planeObj.normal.x, cap.position.y - planeObj.normal.y, cap.position.z - planeObj.normal.z); + cap.lookAt( + cap.position.x - planeObj.normal.x, + cap.position.y - planeObj.normal.y, + cap.position.z - planeObj.normal.z + ); y++; } @@ -318,7 +512,13 @@ export const addColorToClippedMesh = (scene, group, planesNegated, planes, negat }); }; -const createPlaneColored = (planes, plane, color, renderOrder, negatedClick) => { +const createPlaneColored = ( + planes, + plane, + color, + renderOrder, + negatedClick +) => { const capMat = new THREE.MeshStandardMaterial({ color: color, metalness: 0.1, From 765ee0ccb3ac687b8dd02b81e103b5f80aab2d76 Mon Sep 17 00:00:00 2001 From: Angela Busato Date: Tue, 13 Dec 2022 10:19:26 +0100 Subject: [PATCH 3/4] feat: add import file and add planes --- index.html | 3 + index.js | 184 +++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 131 insertions(+), 56 deletions(-) diff --git a/index.html b/index.html index de6d2b0..3508208 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,10 @@
+ Load STL: + +