168 lines
6.1 KiB
JavaScript
168 lines
6.1 KiB
JavaScript
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'
|
||
|
||
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
|
||
},
|
||
|
||
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
|
||
|
||
}
|
||
|
||
},
|
||
|
||
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 = obj.parent
|
||
}
|
||
return obj
|
||
},
|
||
} |