import * as THREE from '/app/thirdparty/Three/three.module.js' import { EffectComposer } from '/app/thirdparty/Three/postprocessing/EffectComposer.module.js' import { RenderPass } from '/app/thirdparty/Three/postprocessing/RenderPass.module.js' import { OutlinePass } from '/app/thirdparty/Three/postprocessing/OutlinePass.module.js' import { ShaderPass } from '/app/thirdparty/Three/postprocessing/ShaderPass.module.js' import { GammaCorrectionShader } from '/app/thirdparty/Three/shaders/GammaCorrectionShader.module.js' if(!app.helpers) app.helpers = {} /** * Mixing add-in methods to your view instance. * All of this should not be a helper, but inherited this from WindozDomContent, but not my framework anymore. */ app.helpers.helpers3D = { agentFromJSON(id, desc){ let obj, wrapper if(desc.type === 'Mesh') { const geom = new THREE[desc.geometry.type](...(desc.geometry.args || [])) const matType = desc.material.type const matProps = { ...desc.material } for (const key in matProps) { if (key === 'type') continue if (typeof matProps[key] === 'string' && THREE[matProps[key]] !== undefined) { matProps[key] = THREE[matProps[key]] } } // convert color strings like "0xffffaa" to numbers if (typeof matProps.color === 'string' && matProps.color.startsWith('0x')) { matProps.color = parseInt(matProps.color) } const mat = new THREE[matType](matProps) obj = new THREE.Mesh(geom, mat) if(desc.translate){ wrapper = new THREE.Object3D() wrapper.add(obj) obj.position.x = desc.translate[0] obj.position.y = desc.translate[1] obj.position.z = desc.translate[2] } } else if(desc.type === 'Group') { obj = new THREE.Group() } else { throw new Error("Unknown type: " + desc.type) } // Apply transforms if(desc.position) obj.position.set(...desc.position) if(desc.rotation) obj.rotation.set(...desc.rotation) if(desc.scale) obj.scale.set(...desc.scale) // Recursively add children if(desc.children) { desc.children.forEach(childDesc => { const childId = (childDesc.childSuffix) ? `${id}_${childDesc.childSuffix}` : '' obj.add(this.agentFromJSON(childId, childDesc)) }) } if(wrapper) obj=wrapper obj.name = id return obj }, resizeRendererToDisplaySize() { const width = this.canvasEl.clientWidth const height = this.canvasEl.clientHeight // Check if renderer size differs from displayed size const needResize = this.canvasEl.width !== width || this.canvasEl.height !== height if (needResize) { // 1. Update renderer (base framebuffer) this.renderer.setSize(width, height, false) // 2️. Update camera aspect ratio this.camera.aspect = width / height this.camera.updateProjectionMatrix() // 3️. Update postprocessing chain if (this.composer) this.composer.setSize(width, height) if (this.outlinePass) this.outlinePass.setSize(width, height) } return needResize }, cameraAutoFrame(object, camera, offset = 1.5, controls) { const box = new THREE.Box3().setFromObject(object) const size = new THREE.Vector3() const center = new THREE.Vector3() box.getSize(size) box.getCenter(center) const maxDim = Math.max(size.x, size.y, size.z) const fov = camera.fov * Math.PI / 180 let cameraZ = maxDim / (2 * Math.tan(fov / 2)) * offset camera.position.copy(center) camera.position.z += cameraZ camera.lookAt(center) if (controls) { controls.target.copy(center) controls.update() } }, getObjectCenter(object) { object.updateWorldMatrix(true, true) const box = new THREE.Box3().setFromObject(object) const center = new THREE.Vector3() box.getCenter(center) // world coords return center }, getObjectTopCenter(object, offset = 0.1) { object.updateWorldMatrix(true, true) const box = new THREE.Box3().setFromObject(object) const center = new THREE.Vector3() box.getCenter(center) const up = new THREE.Vector3(0, 1, 0).applyQuaternion(object.quaternion).normalize() const height = box.max.y - box.min.y const top = center.clone().addScaledVector(up, height / 2 + offset) return top }, init3DHighlighter(options){ if (!this.composer) { this.composer = new EffectComposer(this.renderer) this.composer.addPass(new RenderPass(this.scene, this.camera)) } if (!this.outlinePass) { this.outlinePass = new OutlinePass( new THREE.Vector2(this.canvasEl.innerWidth, this.canvasEl.innerHeight), this.scene, this.camera ) this.outlinePass.edgeStrength = options.edgeStrength || 3 this.outlinePass.visibleEdgeColor.set(options.visibleEdgeColor || 0xffffff) this.outlinePass.edgeGlow = options.edgeGlow || 0 this.outlinePass.edgeThickness = options.edgeThickness || 1 this.outlinePass.pulsePeriod = options.pulsePeriod || 0 this.composer.addPass(this.outlinePass) this.highlighted3DObjects = [] this.outlinePass.selectedObjects = this.highlighted3DObjects this.composer.addPass(new ShaderPass(GammaCorrectionShader)) } }, makePivotAtGeomCenter(object, scene) { object.updateWorldMatrix(true, true) const box = new THREE.Box3().setFromObject(object) const centerW = box.getCenter(new THREE.Vector3()) // Create pivot at world center of the object const pivot = new THREE.Object3D() pivot.position.copy(centerW) pivot.matrixAutoUpdate = true scene.add(pivot) // Compute the center in the object's local space const centerLocal = object.worldToLocal(centerW.clone()) // Shift the object so its center sits at its own origin object.position.sub(centerLocal) // Reparent under the pivot (world pose preserved) pivot.add(object) return pivot }, getNamedParent(obj) { while (obj && ((!obj.name) || (obj.name.includes('_'))) ) { obj = obj.parent } return obj }, createArrow(origin, vector, name='', color = 0xffaa00, headLength = 0.25, headWidth = 0.1) { //const from = new THREE.Vector3(origin.x, origin.y, origin.z) const dir = new THREE.Vector3(vector.x, vector.y, vector.z).normalize() const length = Math.sqrt(vector.x**2 + vector.y**2 + vector.z**2) if(length==0) return(null) const arrow = new THREE.ArrowHelper(dir, origin, length, color, headLength, headWidth) // Optional: add a subtle tube body for style const shaftGeo = new THREE.CylinderGeometry(0.02, 0.02, length - headLength, 16) const shaftMat = new THREE.MeshStandardMaterial({ color, metalness: 0.3, roughness: 0.2 }) const shaft = new THREE.Mesh(shaftGeo, shaftMat) shaft.position.copy(origin.clone().add(dir.clone().multiplyScalar((length - headLength) / 2))) shaft.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir) const group = new THREE.Group() group.add(arrow) group.add(shaft) if(name) group.name = name this.scene.add(group) return group }, }