add normals visualization

This commit is contained in:
Stewart Allen 2022-01-14 14:14:13 -05:00
commit c3049ba546
5 changed files with 147 additions and 10 deletions

View file

@ -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':

View file

@ -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,

View file

@ -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);

View file

@ -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

View file

@ -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);
}
};
})();