diff --git a/index.html b/index.html index 8414e6a..845e948 100644 --- a/index.html +++ b/index.html @@ -4,9 +4,18 @@ My first three.js app diff --git a/interaction.js b/interaction.js index 92541f4..c199d75 100644 --- a/interaction.js +++ b/interaction.js @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { render } from "./main.js"; +import { render, renderer } from "./main.js"; import { getAllImages } from "./single-image-loader.js"; import { createPlane, setScene as setPlaneScene } from "./plane.js"; import { createSphere, setScene as setSphereScene } from "./sphere.js"; @@ -78,12 +78,23 @@ function onClick() { event.preventDefault(); // Avoid clicking images behind GUI if (event.target.tagName !== "CANVAS") return; - mouse.x = (event.clientX / window.innerWidth) * 2 - 1; + // get x,y coords into canvas where click occurred + var rect = event.target.getBoundingClientRect(); + var x = event.clientX - rect.left; + var y = event.clientY - rect.top; + mouse.x = (x / event.target.clientWidth) * 2 - 1; + mouse.y = -(y / event.target.clientHeight) * 2 + 1; + + /*mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; + */ raycaster.setFromCamera(mouse, camera); var intersects = raycaster.intersectObject(scene, true); - if (intersects.length == 0) return; + if (intersects.length == 0) { + console.log("No intersection detected."); + return; + } if (!authoringMode) { openFigure(intersects); @@ -153,8 +164,19 @@ function onHover() { event.preventDefault(); // Avoid clicking images behind GUI if (event.target.tagName !== "CANVAS") return; + + // get x,y coords into canvas where click occurred + var rect = event.target.getBoundingClientRect(); + var x = event.clientX - rect.left; + var y = event.clientY - rect.top; + mouse.x = (x / event.target.clientWidth) * 2 - 1; + mouse.y = -(y / event.target.clientHeight) * 2 + 1; + + /* mouse.x = (event.clientX / window.innerWidth) * 2 - 1; - mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; + mouse.y = -(event.clientY / (window.innerHeight)) * 2 + 1; + */ + //console.log(mouse); raycaster.setFromCamera(mouse, camera); var intersects = raycaster.intersectObject(scene, true); if (intersects.length > 0) { @@ -188,7 +210,7 @@ function createJSON(objectArray) { objectArray.forEach((object) => { const real_pos = get2DCoords(C, object.position); - if (object.userData.intersection == null) intersection_pos = real_pos; + //if (object.userData.intersection == null) intersection_pos = real_pos; const intersection_pos = get2DCoords(C, object.userData.intersection); json.push({ name: object.name, diff --git a/main.js b/main.js index e49e335..13a8051 100644 --- a/main.js +++ b/main.js @@ -12,6 +12,7 @@ import { setScene } from "./inspect.js"; THREE.Cache.enabled = true; const scene = new THREE.Scene(); +window.scene = scene; // debug from console scene.background = new THREE.Color(0xdadada); const camera = new THREE.PerspectiveCamera( @@ -25,7 +26,7 @@ camera.position.set(5, 5, 5); const renderer = new THREE.WebGLRenderer({ antialias: true, }); -renderer.setSize(window.innerWidth, window.innerHeight - 25); +renderer.setSize(window.innerWidth, window.innerHeight - 25, false); // -25 to avoid scroll bar document.body.appendChild(renderer.domElement); const controls = new OrbitControls(camera, renderer.domElement); @@ -55,6 +56,12 @@ scene.add(axesHelper); createPanel(); +await loadImages( + scene, + "out-files/MNAC-AbsidiolaSud/MNAC-AbsSud-CamerasList-converted.lst", + "out-files/MNAC-AbsidiolaSud/MNAC-AbsSud-CamerasRegistration.out" +); + //MODEL LOADER const gltfLoader = new GLTFLoader(); //gltfLoader.load("models/pedret10/MNAC-AbsSud-LowPoly.glb", (object) => { @@ -77,32 +84,31 @@ gltfLoader.load("models/pedret/pedret_XII_text4K.glb", (object) => { 0.0, 1.0 ); - const pos = new THREE.Vector3().setFromMatrixPosition(matrix); - const scale = new THREE.Vector3().setFromMatrixScale(matrix); - const rotation = new THREE.Quaternion().setFromRotationMatrix(matrix); + const rotMat = new THREE.Matrix4().copy(matrix); + rotMat.premultiply(new THREE.Matrix4().makeRotationX(-Math.PI / 2)); + const pos = new THREE.Vector3().setFromMatrixPosition(rotMat); + const scale = new THREE.Vector3().setFromMatrixScale(rotMat); + const rotation = new THREE.Quaternion().setFromRotationMatrix(rotMat); console.log(object.scene); object.scene.position.copy(pos); object.scene.scale.copy(scale); object.scene.quaternion.copy(rotation); - object.scene.name = "model"; - + //console.log("Before:", object.scene.matrixWorld); + /* const wrapper = new THREE.Object3D(); - wrapper.name = "model"; + wrapper.name = "wrapper"; wrapper.add(object.scene); - wrapper.rotateX(-Math.PI / 2); - - scene.add(wrapper); + //wrapper.rotateX(-Math.PI / 2); + scene.add(wrapper);*/ + scene.add(object.scene); + //setIntersectionPosition(object.scene); setIntersectionPosition(scene); }); -await loadImages( - scene, - "out-files/MNAC-AbsidiolaSud/MNAC-AbsSud-CamerasList-converted.lst", - "out-files/MNAC-AbsidiolaSud/MNAC-AbsSud-CamerasRegistration.out" -); + addInteraction(camera, scene, controls); @@ -131,4 +137,4 @@ function render() { animate(); -export { render }; +export { render, renderer }; diff --git a/multiple-image-loader.js b/multiple-image-loader.js index cb227b7..a9fbec8 100644 --- a/multiple-image-loader.js +++ b/multiple-image-loader.js @@ -24,7 +24,7 @@ async function loadImages(scene, images_file, cameras_file) { const out_file_loader = new THREE.FileLoader(); out_file_loader.load(cameras_file, function (data) { const lines = data.split("\n"); - const num_cameras = lines[1].split(" ")[0]; + const num_cameras = lines[1].split(" ")[0]; // TODO for (let i = 0; i < num_cameras; i++) { const line_number = 2 + 5 * i; @@ -34,6 +34,7 @@ async function loadImages(scene, images_file, cameras_file) { const focalLength = lines[line_number].split(" ").map(parseFloat)[0]; const zoom = math.sqrt(focalLength / min_focal); + //zoom *= 2; // TODO testing const R = math.matrix([ lines[line_number + 1].split(" ").map(parseFloat), @@ -41,6 +42,9 @@ async function loadImages(scene, images_file, cameras_file) { lines[line_number + 3].split(" ").map(parseFloat), ]); const t = math.matrix(lines[line_number + 4].split(" ").map(parseFloat)); + + //if (!image_list[i].includes("0098")) continue; // TODO + loadImage(scene, R, t, zoom, image_list[i], image_loader); } }); diff --git a/openseadragon.html b/openseadragon.html index 8779264..be69895 100644 --- a/openseadragon.html +++ b/openseadragon.html @@ -8,12 +8,10 @@
- Center camera - Zoom In - Zoom Out - Full Screen - Increase Deatils Reset camera + Full Screen + + Increase Details Use camera positions - Set intersection position Use camera targets + Distribute + +
diff --git a/openseadragon.js b/openseadragon.js index 0cae8ae..db9fe6a 100644 --- a/openseadragon.js +++ b/openseadragon.js @@ -8,6 +8,7 @@ const parsedImages = JSON.parse(retrievedObject); var overlapping; var regularZoom = true; var realPosition = true; +var overlappingSet = false; console.log(mode); console.log(image); @@ -37,8 +38,6 @@ if (mode === "single") { } var viewer = OpenSeadragon({ - zoomInButton: "zoom-in", - zoomOutButton: "zoom-out", homeButton: "home", fullPageButton: "full-page", @@ -48,13 +47,14 @@ var viewer = OpenSeadragon({ showNavigator: true, preserveViewport: true, + maxZoomPixelRatio: 3, // for videos }); viewer.zoomPerClick = 1; viewer.addHandler("open", function () { if (mode === "single" || parsedImages.size == 1) return; - distribute(parsedImages); + //distribute(parsedImages); // testing }); viewer.addHandler("canvas-click", function (event) { @@ -84,7 +84,25 @@ viewer.addHandler("canvas-click", function (event) { } }); +function distrib() { + if (mode === "single" || parsedImages.size == 1) return; + overlappingSet = !overlappingSet; + + if (overlappingSet) { + document.getElementById("overlap").style.display = "inline"; + document.getElementById("distribute").style.display = "none"; + } else { + document.getElementById("overlap").style.display = "none"; + document.getElementById("distribute").style.display = "inline"; + } + + recalculate(); + distribute(parsedImages); +} + function distribute(images) { + if (!overlappingSet) return; + //return; // TODO overlapping = true; for (let i = 0; overlapping; i++) { console.log("i: " + i); @@ -128,6 +146,12 @@ function getIntersection(a, b) { var right = min(getRight(a), getRight(b)); var bottom = max(getBottom(a), getBottom(b)); + const margin = 0.01; + left -= margin; + right += margin; + top += margin; + bottom -= margin; + if (bottom < top && right > left) { return { top: top, @@ -143,8 +167,9 @@ function getIntersection(a, b) { } function moveImage(a, output, i) { - a.x += output.x; - a.y += output.y; + const speed = 1.0; // testing + a.x += output.x * speed; + a.y += output.y * speed; var item = viewer.world.getItemAt(i); item.setPosition(new OpenSeadragon.Point(a.x - getWidth(a) / 2, a.y - getHeight(a) / 2)); } @@ -178,6 +203,9 @@ function togglePosition() { } function recalculate() { + console.log( + "Recalculating. Real position: " + realPosition + ". Regular zoom: " + regularZoom + "." + ); for (let i = 0; i < parsedImages.length; i++) { const a = parsedImages[i]; var item = viewer.world.getItemAt(i); diff --git a/package-lock.json b/package-lock.json index dd4c73c..e0a6f41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,8 @@ "": { "dependencies": { "mathjs": "^12.3.1", - "three": "^0.161.0" + "three": "^0.165.0", + "three-mesh-bvh": "^0.7.5" }, "devDependencies": { "vite": "^5.0.12" @@ -783,9 +784,17 @@ } }, "node_modules/three": { - "version": "0.161.0", - "resolved": "https://registry.npmjs.org/three/-/three-0.161.0.tgz", - "integrity": "sha512-LC28VFtjbOyEu5b93K0bNRLw1rQlMJ85lilKsYj6dgTu+7i17W+JCCEbvrpmNHF1F3NAUqDSWq50UD7w9H2xQw==" + "version": "0.165.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.165.0.tgz", + "integrity": "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA==" + }, + "node_modules/three-mesh-bvh": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.5.tgz", + "integrity": "sha512-WDd77RklE52pZSKZx8sDXzrd2JCF/gL/hugFvsIBylpMRlJxxwesKn2rW7TcQZ809NocDVkQx1UJo9pJtVAPYg==", + "peerDependencies": { + "three": ">= 0.151.0" + } }, "node_modules/tiny-emitter": { "version": "2.1.0", diff --git a/package.json b/package.json index 7114204..3283552 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "dependencies": { "mathjs": "^12.3.1", - "three": "^0.161.0" + "three": "^0.165.0", + "three-mesh-bvh": "^0.7.5" }, "devDependencies": { "vite": "^5.0.12" diff --git a/single-image-loader.js b/single-image-loader.js index 7b64db7..c0d23d4 100644 --- a/single-image-loader.js +++ b/single-image-loader.js @@ -1,14 +1,23 @@ import * as THREE from "three"; import { create, all } from "mathjs"; +import { MeshBVH, acceleratedRaycast, computeBoundsTree, disposeBoundsTree } from "three-mesh-bvh"; // BVH +THREE.Mesh.prototype.raycast = acceleratedRaycast; +THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree; +THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree; + const math = create(all, {}); var raycaster = new THREE.Raycaster(); +raycaster.params.Line.threshold = 0.001; + var imageOffset = 0.2; var imageSize = 1; var images = []; +const pickableObjects = []; + function loadImage(scene, R, t, zoom, image_name, image_loader) { const pos = math.multiply(math.unaryMinus(math.transpose(R)), t); //View direction @@ -60,7 +69,7 @@ function loadImage(scene, R, t, zoom, image_name, image_loader) { color: 0x0000ff, transparent: true, opacity: 0.5, - linewidth: 0.1, + linewidth: 0.2, }); const line = new THREE.Line(geometry, material); line.name = "wireframe-line"; @@ -95,6 +104,15 @@ function loadImage(scene, R, t, zoom, image_name, image_loader) { isLandscape: isLandscape, heightToWidthRatio: heightToWidthRelation, }; + console.log( + "Image: ", + image_name, + " Position: ", + image_plane.position, + " Direction: ", + image_plane.userData.direction + ); + images.push(image_plane); }); } @@ -132,24 +150,74 @@ function setWireframe(enable) { } function setIntersectionPosition(scene) { + console.log(scene); + + var gltf = scene.getObjectByName("model"); + gltf.updateMatrixWorld(true); + gltf.traverse(function (child) { + if (child.isMesh) { + const m = child; // as THREE.Mesh + m.geometry.computeBoundsTree(); // BVH + pickableObjects.push(m); + } + }); + console.log("Pick:", pickableObjects); + + var model = scene.getObjectByName("model"); + //var model = scene; + console.log(model); + console.log("Setting intersection positions for ", images.length, " images..."); + images.forEach((i) => { const intersectionPosition = getIntersectionPosition( scene, + model, + pickableObjects, i.position, i.userData.direction ); i.userData.intersection = new THREE.Vector3().copy(intersectionPosition); }); + console.log("Setting intersection positions: done!"); } -function getIntersectionPosition(scene, position, direction) { +function getIntersectionPosition(scene, model, objs, position, direction) { raycaster.set(position, direction); - var intersections = raycaster.intersectObject(scene, true); - if (intersections.length == 0) return position; - for (let i = 0; i < intersections.length; i++) { - if (intersections[i].object.name != "wireframe") return intersections[i].point; + console.log("Position: ", position, " Direction: ", direction); + raycaster.firstHitOnly = true; // BVH + //var intersections = raycaster.intersectObjects(model, true); // BVH + /* + const invMat = new THREE.Matrix4(); + invMat.copy(model.matrixWorld).invert(); + raycaster.ray.applyMatrix4(invMat); + */ + + var intersections = raycaster.intersectObjects(objs, true); // BVH + + if (intersections.length == 0) { + console.log("No intersection found"); + return position; } - return position; + var i = 0; + var j = -1; + for (; i < intersections.length; i++) { + //if (intersections[i].object.name != "wireframe" && intersections[i].object.name != "wireframe-line" && intersections[i].object.name != "" && intersections[i].object.name[0] != "S") break; // return intersections[i].point; + console.log("Intersected:", intersections[i].object); + //if (intersections[i].object.name != "wireframe") j = i; + if (intersections[i].object.name[0] == "P") { + j = i; + break; // return intersections[i].point; + } + } + if (j != -1) + console.log( + "Position: ", + position, + " Intersection", + intersections[j].point, + intersections[j].object.name + ); + return intersections[j].point; } function setImageVisibility(show) { diff --git a/sphere.js b/sphere.js index 64aa7f2..b2d71b0 100644 --- a/sphere.js +++ b/sphere.js @@ -56,7 +56,7 @@ function get2DCoords(P) { const V = new THREE.Vector3().subVectors(P, C).normalize(); const phi = math.acos(V.y); const theta = math.atan2(V.x, V.z); - return { x: theta, y: phi }; + return { x: -theta, y: phi }; // TODO: Check if it is correct } function applySphericalRadius(r) {