From c3049ba5460313c12be821d171a51fba78b5957f Mon Sep 17 00:00:00 2001 From: Stewart Allen Date: Fri, 14 Jan 2022 14:14:13 -0500 Subject: [PATCH] add normals visualization --- src/main/mesh.js | 4 +- src/mesh/api.js | 18 ++++++++- src/mesh/group.js | 6 +++ src/mesh/model.js | 36 +++++++++++++++--- src/mesh/util.js | 93 ++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 147 insertions(+), 10 deletions(-) diff --git a/src/main/mesh.js b/src/main/mesh.js index be1f2a72..b2763461 100644 --- a/src/main/mesh.js +++ b/src/main/mesh.js @@ -175,10 +175,12 @@ function space_init(data) { return api.tool.analyze(); case 'KeyR': return api.tool.repair(); - case 'KeyN': + case 'KeyE': return api.tool.clean(); case 'KeyV': return selection.focus(); + case 'KeyN': + return api.normals(); case 'KeyW': return api.wireframe(); case 'KeyG': diff --git a/src/mesh/api.js b/src/mesh/api.js index 19e405a2..8a85f20a 100644 --- a/src/mesh/api.js +++ b/src/mesh/api.js @@ -128,7 +128,8 @@ let selection = { update() { for (let group of groups) { group.select(false); - group.wireframe(prefs.map.space.wire); + group.wireframe(prefs.map.space.wire || false); + group.normals(prefs.map.space.norm || false); } // prevent selection of model and its group let mgsel = selected.filter(s => s instanceof mesh.model).map(m => m.group); @@ -491,12 +492,25 @@ let api = mesh.api = { } else { wire = state; } - for (let m of api.model.list()) { + for (let m of model.list()) { m.wireframe(wire, opt); } prefs.save( prefs.map.space.wire = wire ); }, + normals(state = {toggle:true}) { + let norm = prefs.map.space.norm; + if (state.toggle) { + norm = !norm; + } else { + norm = state; + } + for (let m of model.list()) { + m.normals(norm); + } + prefs.save( prefs.map.space.norm = norm ); + }, + mode, modes, diff --git a/src/mesh/group.js b/src/mesh/group.js index 75517217..00ff7234 100644 --- a/src/mesh/group.js +++ b/src/mesh/group.js @@ -122,6 +122,12 @@ mesh.group = class MeshGroup extends mesh.object { } } + normals() { + for (let model of this.models) { + model.normals(...arguments); + } + } + select() { for (let model of this.models) { model.select(...arguments); diff --git a/src/mesh/model.js b/src/mesh/model.js index 1afc6732..b348ad34 100644 --- a/src/mesh/model.js +++ b/src/mesh/model.js @@ -55,7 +55,9 @@ let materials = mesh.material = { wireframe: new MeshBasicMaterial({ side: DoubleSide, wireframe: true, - color: 0x0 + color: 0x0, + transparent: true, + opacity: 0.5 }), }; @@ -249,23 +251,24 @@ mesh.model = class MeshModel extends mesh.object { } wireframe(bool, opt = {}) { + let was = this._wire ? true : false; if (bool === undefined) { - return this._wire ? { + return was ? { enabled: true, opacity: this.opacity(), - color: this._wire ? this._wire.material.color : undefined, + color: was ? this._wire.material.color : undefined, } : { enabled: false }; } if (bool.toggle) { - return this.wireframe(this._wire ? false : true, opt); + bool = !was; } - let was = this._wire ? true : false; + // no change if (was === bool) { return was; } - if (this._wire) { + if (was) { this.mesh.remove(this._wire); this._wire = undefined; this.opacity({restore: true}); @@ -279,6 +282,27 @@ mesh.model = class MeshModel extends mesh.object { return was; } + normals(bool) { + let was = this._norm ? true : false; + if (bool === undefined) { + return was; + } + if (bool.toggle) { + bool = !was; + } + // no change + if (was === bool) { + return was; + } + if (was) { + this.mesh.remove(this._norm); + this._norm = undefined; + } + if (bool) { + this.mesh.add(this._norm = mesh.util.faceNormals(this.mesh, { size: 0.5 })); + } + } + remove() { if (arguments.length === 0) { // direct call requires pass through group diff --git a/src/mesh/util.js b/src/mesh/util.js index 6ee38ac4..8172e929 100644 --- a/src/mesh/util.js +++ b/src/mesh/util.js @@ -14,7 +14,7 @@ gapp.register("mesh.util", [ let mesh = self.mesh = self.mesh || {}; if (mesh.util) return; -let { Matrix4, Vector3, Box3 } = THREE; +let { Matrix4, Matrix3, Vector3, Box3 } = THREE; let deferFn = []; let boundsCache = {}; @@ -156,7 +156,98 @@ let util = mesh.util = { avg[f] = (avg[f] || 0) / array.length; } return avg; + }, + + faceNormals(mesh, opt = { }) { + const _va = new THREE.Vector3(); + const _vb = new THREE.Vector3(); + const _vc = new THREE.Vector3(); + const _v1 = new THREE.Vector3(); + const _v2 = new THREE.Vector3(); + const _normalMatrix = new THREE.Matrix3(); + + class FaceNormalsHelper extends THREE.LineSegments { + constructor(object, size = opt.size || 1, color = opt.color || 0xff0000) { + const objGeometry = object.geometry; + const nNormals = objGeometry.attributes.position.count / 3; + const geometry = new THREE.BufferGeometry(); + const positions = new THREE.Float32BufferAttribute(nNormals * 3 * 2, 3); + geometry.setAttribute('position', positions); + super(geometry, new THREE.LineBasicMaterial({ + color, toneMapped: false + })); + this.object = object; + this.size = size; + this.type = 'FaceNormalsHelper'; + this.matrixAutoUpdate = false; + this.update(); + } + + update() { + this.object.updateMatrixWorld(true); + const position = this.geometry.attributes.position; + const objGeometry = this.object.geometry; + const objArr = objGeometry.attributes.position.array; + let j=0; + for (let idx = 0, xj = 0, jl = objArr.length; j < jl; ) { + _va.set(objArr[j++], objArr[j++], objArr[j++]); + _vb.set(objArr[j++], objArr[j++], objArr[j++]); + _vc.set(objArr[j++], objArr[j++], objArr[j++]); + let vn = THREE.computeFaceNormal(_va, _vb, _vc); + let vc = _va.add(_vb).add(_vc).divideScalar(3); + _v2.copy(vc).add(vn.multiplyScalar(this.size)); + position.setXYZ(idx++, vc.x, vc.y, vc.z); + position.setXYZ(idx++, _v2.x, _v2.y, _v2.z); + } + position.needsUpdate = true; + } + } + + return new FaceNormalsHelper(mesh); + }, + + vertexNormals(mesh) { + const _v1 = new Vector3(); + const _v2 = new Vector3(); + const _normalMatrix = new Matrix3(); + + class VertexNormalsHelper extends THREE.LineSegments { + constructor(object, size = 1, color = 0xff0000) { + const objGeometry = object.geometry; + const nNormals = objGeometry.attributes.normal.count; + const geometry = new THREE.BufferGeometry(); + const positions = new THREE.Float32BufferAttribute(nNormals * 2 * 3, 3); + geometry.setAttribute('position', positions); + super(geometry, new THREE.LineBasicMaterial({ + color,toneMapped: false + })); + this.object = object; + this.size = size; + this.type = 'VertexNormalsHelper'; + this.matrixAutoUpdate = false; + this.update(); + } + + update() { + this.object.updateMatrixWorld(true); + const position = this.geometry.attributes.position; + const objGeometry = this.object.geometry; + const objPos = objGeometry.attributes.position; + const objNorm = objGeometry.attributes.normal; + for (let idx = 0, j = 0, jl = objPos.count; j < jl; j++) { + _v1.set(objPos.getX(j), objPos.getY(j), objPos.getZ(j)); + _v2.set(objNorm.getX(j), objNorm.getY(j), objNorm.getZ(j)); + _v2.applyMatrix3(_normalMatrix).normalize().multiplyScalar(this.size).add(_v1); + position.setXYZ(idx++, _v1.x, _v1.y, _v1.z); + position.setXYZ(idx++, _v2.x, _v2.y, _v2.z); + } + position.needsUpdate = true; + } + } + + return new VertexNormalsHelper(mesh); } + }; })();