diff --git a/index.css b/index.css
index da76ed6..e393a7b 100644
--- a/index.css
+++ b/index.css
@@ -7,8 +7,25 @@ canvas {
height: 100%;
}
+.header {
+ height: 100px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ position: absolute;
+ top: 0px;
+ width: 100%;
+ padding: 10px;
+ box-sizing: border-box;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ z-index: 1;
+}
+
.container {
- font-family: 'Roboto', sans-serif;
+ font-family: "Roboto", sans-serif;
}
.input_container {
@@ -22,6 +39,8 @@ canvas {
.input_box {
display: flex;
margin-bottom: 1em;
+ flex-wrap: wrap;
+ gap: 1rem;
}
.input_label {
@@ -62,6 +81,12 @@ canvas {
bottom: 1px;
top: auto;
}
+
+#forkongithub {
+ display: none;
+}
+
+
@media screen and (min-width: 800px) {
#forkongithub {
position: fixed;
@@ -85,9 +110,4 @@ canvas {
-o-transform: rotate(45deg);
box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.8);
}
-
- .input_container {
- width: 50%;
- margin-top: 1.5em;
- }
}
diff --git a/index.html b/index.html
index de6d2b0..81fb99a 100644
--- a/index.html
+++ b/index.html
@@ -11,16 +11,21 @@
Fork me on GitHub
-
-
-
-
-
-
-
-
-
- Hide Plane
+
diff --git a/index.js b/index.js
index 208694d..ff65e90 100644
--- a/index.js
+++ b/index.js
@@ -1,114 +1,354 @@
import * as THREE from "https://threejsfundamentals.org/threejs/resources/threejs/r127/build/three.module.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";
+import { STLLoader } from "https://threejsfundamentals.org/threejs/resources/threejs/r127/examples/jsm/loaders/STLLoader.js";
+import { TransformControls } from "https://threejsfundamentals.org/threejs/resources/threejs/r127/examples/jsm/controls/TransformControls.js";
-import { TrackballControls } from "https://threejsfundamentals.org/threejs/resources/threejs/r127/examples/jsm/controls/TrackballControls.js";
+let container;
+let camera, scene, renderer;
+let controller1, controller2;
+let controllerGrip1, controllerGrip2;
-let renderer, scene, planeMesh, planeMesh2, group;
+let raycaster;
+
+const intersected = [];
+const tempMatrix = new THREE.Matrix4();
+
+let controls, tControls, group;
+
+let planeMesh, planeMesh2;
let planes = [];
let planesOriginal = [];
+init();
+animate();
+
function init() {
+ container = document.createElement("div");
+ document.body.appendChild(container);
+
+ scene = new THREE.Scene();
+ scene.background = new THREE.Color(0x808080);
+
+ camera = new THREE.PerspectiveCamera(
+ 70,
+ window.innerWidth / window.innerHeight,
+ 1,
+ 10000
+ );
+ // camera.position.set(0, 1.6, 3);
+ camera.position.set(0, -200, 100);
+
+ 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.AmbientLight(0xffffff, 0.5));
+
+ // 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 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(geometry1, material);
+ // box1.name = "box1";
+ // box1.position.set(0, 1, 0);
+ // box1.renderOrder = 6;
+
+ // // Create box2
+ // 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(0, 1, 0);
+ // box2.name = "box2";
+ // box2.renderOrder = 6;
+
+ // group.name = "group";
+ // group.add(box1);
+ // group.add(box2);
+
+ // Create plane
+ // 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(0, 1, 0);
+ // planeMesh.rotation.x = Math.PI / 2;
+ // planeMesh.name = "plane";
+ // scene.add(planeMesh);
+
+ // // Create plane2
+ // 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(0, 1, 0);
+ // planeMesh2.name = "plane2";
+ // scene.add(planeMesh2);
+
+ // Renderer
+
renderer = new THREE.WebGLRenderer({ antialias: true });
- renderer.setClearColor(0xf0f0f0);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.outputEncoding = THREE.sRGBEncoding;
+ renderer.shadowMap.enabled = true;
renderer.localClippingEnabled = true;
-
- let container = document.createElement("div");
- document.body.appendChild(container);
+ renderer.xr.enabled = true;
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;
+ document.body.appendChild(VRButton.createButton(renderer));
- let controls = new TrackballControls(camera, container);
- controls.rotateSpeed = 10.0;
+ tControls = new TransformControls(camera, renderer.domElement);
+ tControls.addEventListener("change", render);
- scene = new THREE.Scene();
- scene.add(new THREE.AmbientLight(0x505050));
+ tControls.addEventListener("dragging-changed", function (event) {
+ controls.enabled = !event.value;
+ });
- // LIGHTS
- const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // color, intensity
- scene.add(ambientLight);
+ scene.add(tControls);
- const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
- directionalLight.position.copy(camera.position);
- directionalLight.castShadow = true;
- scene.add(directionalLight);
+ // controllers
- // Create box1
- const geometry = new THREE.BoxGeometry(100, 100, 100);
- const material = new THREE.MeshStandardMaterial({
- color: "#ff0000",
- side: THREE.DoubleSide,
- });
+ 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();
- const box1 = new THREE.Mesh(geometry, material);
- box1.name = "box1";
- box1.renderOrder = 6;
+ //
- // Create box2
- const geometry2 = new THREE.BoxGeometry(70, 70, 70);
- const material2 = new THREE.MeshStandardMaterial({
+ window.addEventListener("resize", onWindowResize);
+}
+
+let mesh, position;
+
+// Load the file and get the geometry
+document.getElementById("file").onchange = (e) => {
+ const files = e.target.files;
+
+ if (files.length > 0) {
+ for (var i = 0; i < files.length; i++) {
+ loadFile(files[i]);
+ }
+ }
+};
+
+const loadFile = (file) => {
+ let reader = new FileReader();
+
+ reader.onload = () => {
+ const geometry = new STLLoader().parse(reader.result);
+
+ createMeshFromFile(geometry);
+ };
+
+ reader.readAsArrayBuffer(file);
+};
+
+/**
+ * Creates the mesh from the file's geometry
+ * @param {THREE.BufferGeometry} geometry
+ */
+const createMeshFromFile = (geometry) => {
+ if (mesh) {
+ scene.remove(mesh);
+ }
+
+ const material = new THREE.MeshLambertMaterial({
color: "#C7AC96",
- side: THREE.DoubleSide,
+ wireframe: false,
});
+ mesh = new THREE.Mesh(geometry, material);
+
+ // saves the position of the first element
+ if (!position) {
+ position = getCenter(mesh);
+ }
- const box2 = new THREE.Mesh(geometry2, material2);
- //box2.position.set(100, 0, 0);
- box2.name = "box2";
- box2.renderOrder = 6;
+ mesh.position.set(-position.x, -position.y, -position.z);
- group = new THREE.Group();
- group.name = "group";
- group.add(box1);
- group.add(box2);
- scene.add(group);
+ group.add(mesh);
+};
- // Create plane
- const planeGeometry = new THREE.PlaneGeometry(200, 200, 1, 1);
- const planeMaterial = new THREE.MeshStandardMaterial({
+document.getElementById("addPlanes").addEventListener("click", () => {
+ const geometry = new THREE.PlaneGeometry(30, 30, 1, 1);
+ const material = new THREE.MeshStandardMaterial({
color: "#38382f",
side: THREE.DoubleSide,
});
+ const mesh = new THREE.Mesh(geometry, material);
+ mesh.name = "plane";
- planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
- //planeMesh.position.set(50, 0, 0);
- planeMesh.rotation.x = Math.PI / 2;
- planeMesh.name = "plane";
- scene.add(planeMesh);
+ scene.add(mesh);
- // Create plane2
- const planeGeometry2 = new THREE.PlaneGeometry(200, 200, 1, 1);
- const planeMaterial2 = new THREE.MeshStandardMaterial({
- color: "#f0f0f0",
- side: THREE.DoubleSide,
- });
+ tControls.attach(mesh);
+ tControls.setMode("translate");
+});
+
+function onWindowResize() {
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+
+ renderer.setSize(window.innerWidth, window.innerHeight);
+}
+
+function onSelectStart(event) {
+ const controller = event.target;
- planeMesh2 = new THREE.Mesh(planeGeometry2, planeMaterial2);
- //planeMesh2.position.set(50, 0, 20);
- planeMesh2.name = "plane2";
- scene.add(planeMesh2);
+ const intersections = getIntersections(controller);
- function onWindowResize() {
- camera.aspect = window.innerWidth / window.innerHeight;
- camera.updateProjectionMatrix();
- renderer.setSize(window.innerWidth, window.innerHeight);
+ 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 render() {
- requestAnimationFrame(render);
- controls.update();
- renderer.render(scene, camera);
+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;
}
+}
- render();
+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(scene.children, false);
}
-init();
+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);
+
+ line.scale.z = intersection.distance;
+ } else {
+ line.scale.z = 5;
+ }
+}
+
+function cleanIntersected() {
+ while (intersected.length) {
+ const object = intersected.pop();
+ object.material.emissive.r = 0;
+ }
+}
+
+//
+
+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");
@@ -116,11 +356,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 = [];
@@ -145,9 +389,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 = [];
@@ -188,7 +435,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));
});
@@ -198,7 +447,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
@@ -237,7 +488,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;
@@ -288,7 +545,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);
@@ -298,16 +561,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++;
}
@@ -315,7 +594,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,
@@ -348,3 +633,12 @@ const getCenterPoint = (mesh) => {
mesh.localToWorld(center);
return center;
};
+
+const getCenter = (object) => {
+ const center = new THREE.Vector3();
+
+ const box3 = new THREE.Box3().setFromObject(object);
+ box3.getCenter(center);
+
+ return center;
+};