mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-15 21:05:22 +01:00
improve edges rendering
This commit is contained in:
parent
3cdd4b09f0
commit
8b99c186dc
10 changed files with 232 additions and 114 deletions
7
modules/math/vectorAdapters.js
Normal file
7
modules/math/vectorAdapters.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import Vector from './vector';
|
||||
import {Vector3} from 'three';
|
||||
|
||||
export const arrToThree = arr => new Vector3().fromArray(arr);
|
||||
export const arrToVector = arr => new Vector().set3(arr);
|
||||
export const threeToVector = threeV => new Vector().set(threeV);
|
||||
|
||||
131
modules/scene/objects/scalableLine.js
Normal file
131
modules/scene/objects/scalableLine.js
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import {Face3, FaceColors, Geometry, Mesh, MeshBasicMaterial, MeshPhongMaterial} from 'three';
|
||||
import {advancePseudoFrenetFrame, frenetFrame, pseudoFrenetFrame} from '../../../web/app/brep/geom/curves/frenetFrame';
|
||||
import * as vec from '../../../web/app/math/vec';
|
||||
import {viewScaleFactor} from '../scaleHelper';
|
||||
import {arrToThree} from 'math/vectorAdapters';
|
||||
import {ORIGIN} from '../../../web/app/math/l3space';
|
||||
import {getSceneSetup} from '../sceneSetup';
|
||||
import calcFaceNormal from '../utils/calcFaceNormal';
|
||||
|
||||
export default class ScalableLine extends Mesh {
|
||||
|
||||
constructor(tesselation, width, color, opacity, smooth, ambient) {
|
||||
super(createGeometry(tesselation, smooth), createMaterial(color, opacity, ambient));
|
||||
this.width = width;
|
||||
this.morphTargetInfluences[0] = 0;
|
||||
}
|
||||
|
||||
updateMatrix() {
|
||||
let sceneSetup = getSceneSetup(this);
|
||||
if (!sceneSetup) {
|
||||
return;
|
||||
}
|
||||
let modelSize = 1;
|
||||
let modelSizePx = this.width;
|
||||
let k = viewScaleFactor(sceneSetup, ORIGIN, modelSizePx, modelSize);
|
||||
let morphDistance = (k * modelSize - modelSize) / 2;
|
||||
this.morphTargetInfluences[0] = morphDistance / morphBase;
|
||||
super.updateMatrix();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.geometry.dispose();
|
||||
this.material.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
function createMaterial(color, opacity, ambient) {
|
||||
let materialParams = {
|
||||
vertexColors: FaceColors,
|
||||
morphTargets: true,
|
||||
color,
|
||||
};
|
||||
if (!ambient) {
|
||||
materialParams.shininess = 0;
|
||||
}
|
||||
if (opacity !== undefined) {
|
||||
materialParams.transparent = true;
|
||||
materialParams.opacity = opacity;
|
||||
}
|
||||
return ambient ? new MeshBasicMaterial(materialParams) : new MeshPhongMaterial(materialParams);
|
||||
}
|
||||
|
||||
function createGeometry(tessellation, smooth) {
|
||||
const width = 1;
|
||||
const geometry = new Geometry();
|
||||
const scaleTargets = [];
|
||||
const morphBase = 10;
|
||||
geometry.dynamic = true;
|
||||
let tess = tessellation;
|
||||
|
||||
// let frames = [pseudoFrenetFrame(edge.curve.tangentAtPoint(new Vector().set3(tess[0])).data())];
|
||||
let frames = [pseudoFrenetFrame(vec._normalize(vec.sub(tess[1], tess[0])))];
|
||||
// let frames = [calcFrame(tess[0]) || pseudoFrenetFrame(edge.curve.tangentAtPoint(new Vector().set3(tess[0])).data())];
|
||||
|
||||
for (let i = 1; i < tess.length; i++) {
|
||||
let a = tess[i - 1];
|
||||
let b = tess[i];
|
||||
let ab = vec._normalize(vec.sub(b, a));
|
||||
let prevFrame = frames[i - 1];
|
||||
let T = vec._normalize(vec.add(prevFrame[0], ab));
|
||||
// frames.push(calcFrame(b) || advancePseudoFrenetFrame(prevFrame, T));
|
||||
frames.push(advancePseudoFrenetFrame(prevFrame, T));
|
||||
}
|
||||
|
||||
let axises = frames.map(([T, N, B]) => {
|
||||
let dirs = [];
|
||||
dirs[0] = N;
|
||||
dirs[1] = B;
|
||||
dirs[2] = vec.negate(dirs[0]);
|
||||
dirs[3] = vec.negate(dirs[1]);
|
||||
return dirs;
|
||||
});
|
||||
|
||||
let normals = smooth ? [] : null;
|
||||
|
||||
axises.forEach((dirs, i) => {
|
||||
dirs.forEach(dir => {
|
||||
geometry.vertices.push(arrToThree(vec._add(vec.mul(dir, width), tess[i])));
|
||||
scaleTargets.push(arrToThree(vec._add(vec.mul(dir, width + morphBase), tess[i])));
|
||||
if (smooth) {
|
||||
normals.push(arrToThree(dir));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
for (let i = 0; i < tess.length - 1; i++) {
|
||||
let off = 4 * i;
|
||||
[
|
||||
[0, 4, 3],
|
||||
[3, 4, 7],
|
||||
[2, 3, 7],
|
||||
[7, 6, 2],
|
||||
[0, 1, 5],
|
||||
[5, 4, 0],
|
||||
[1, 2, 6],
|
||||
[6, 5, 1],
|
||||
].forEach(([a, b, c]) => {
|
||||
let vertexNormales = smooth ? [normals[a + off], normals[b + off], normals[c + off]] : undefined;
|
||||
let face = new Face3(a + off, b + off, c + off, vertexNormales);
|
||||
geometry.faces.push(face);
|
||||
if (!smooth) {
|
||||
calcFaceNormal(face, geometry.vertices);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let startNormal = arrToThree(frames[0][0]).negate();
|
||||
geometry.faces.push(new Face3(2, 1, 0, startNormal));
|
||||
geometry.faces.push(new Face3(0, 3, 2, startNormal));
|
||||
|
||||
let endNormal = arrToThree(frames[frames.length - 1][0]);
|
||||
let n = frames.length * 4 - 1;
|
||||
geometry.faces.push(new Face3(n - 2, n - 1, n, endNormal));
|
||||
geometry.faces.push(new Face3(n, n - 3, n - 2, endNormal));
|
||||
|
||||
geometry.morphTargets.push({name: 'scaleTargets', vertices: scaleTargets});
|
||||
return geometry;
|
||||
}
|
||||
|
||||
const morphBase = 10;
|
||||
22
modules/scene/scaleHelper.js
Normal file
22
modules/scene/scaleHelper.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import {Vector3} from 'three';
|
||||
import DPR from '../dpr';
|
||||
|
||||
export function viewScaleFactor(sceneSetup, origin, SIZE_PX, SIZE_MODEL) {
|
||||
let container = sceneSetup.container;
|
||||
let viewHeight = container.clientHeight;
|
||||
let camera = sceneSetup.camera;
|
||||
|
||||
if (camera.isOrthographicCamera) {
|
||||
return viewHeight / (camera.top - camera.bottom) / camera.zoom * 2 * DPR * SIZE_PX / SIZE_MODEL;
|
||||
} else {
|
||||
let p = new Vector3().copy(origin);
|
||||
let cp = new Vector3().copy(camera.position);
|
||||
let z = p.sub(cp).length();
|
||||
let tanHFov = Math.atan((camera.fov / 2) / 180 * Math.PI);
|
||||
let fitUnits = tanHFov * z * 2;
|
||||
|
||||
let modelTakingPart = SIZE_MODEL / fitUnits;
|
||||
let modelActualSizePx = viewHeight * modelTakingPart;
|
||||
return SIZE_PX / modelActualSizePx;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ export default class SceneSetUp {
|
|||
this.scene = new THREE.Scene();
|
||||
this.rootGroup = this.scene;
|
||||
this.onRendered = onRendered;
|
||||
this.scene.userData.sceneSetUp = this;
|
||||
|
||||
this.setUpCamerasAndLights();
|
||||
this.setUpControls();
|
||||
|
|
@ -223,4 +224,14 @@ export default class SceneSetUp {
|
|||
}
|
||||
}
|
||||
|
||||
export function getSceneSetup(object3D) {
|
||||
do {
|
||||
if (object3D.userData.sceneSetUp) {
|
||||
return object3D.userData.sceneSetUp;
|
||||
}
|
||||
object3D = object3D.parent;
|
||||
} while(object3D);
|
||||
return null;
|
||||
}
|
||||
|
||||
const ORTHOGRAPHIC_CAMERA_FACTOR = 1;
|
||||
12
modules/scene/utils/calcFaceNormal.js
Normal file
12
modules/scene/utils/calcFaceNormal.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import {Vector3} from 'three';
|
||||
|
||||
export default function(face, vertices) {
|
||||
let ab = new Vector3();
|
||||
let vA = vertices[ face.a ];
|
||||
let vB = vertices[ face.b ];
|
||||
let vC = vertices[ face.c ];
|
||||
face.normal.subVectors( vC, vB );
|
||||
ab.subVectors( vA, vB );
|
||||
face.normal.cross( ab );
|
||||
face.normal.normalize();
|
||||
}
|
||||
22
web/app/brep/geom/curves/frenetFrame.js
Normal file
22
web/app/brep/geom/curves/frenetFrame.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import * as vec from '../../../math/vec';
|
||||
import {perpendicularVector} from '../../../math/math';
|
||||
|
||||
export function frenetFrame(D1, D2) {
|
||||
let T = vec.normalize(D1);
|
||||
let N = vec.normalize(D2);
|
||||
let B = vec.cross(T, N);
|
||||
return [T, N, B];
|
||||
}
|
||||
|
||||
export function pseudoFrenetFrame(D1) {
|
||||
let T = vec.normalize(D1);
|
||||
let N = perpendicularVector(T);
|
||||
let B = vec.cross(T, N);
|
||||
return [T, N, B];
|
||||
}
|
||||
|
||||
export function advancePseudoFrenetFrame(refFrame, T) {
|
||||
let B = vec._normalize(vec.cross(T, refFrame[1]));
|
||||
let N = vec.cross(B, T);
|
||||
return [T, N, B];
|
||||
}
|
||||
|
|
@ -1,11 +1,7 @@
|
|||
import {
|
||||
Geometry, Line, LineBasicMaterial, MeshBasicMaterial, MeshLambertMaterial, Object3D, Quaternion,
|
||||
Vector3
|
||||
} from 'three';
|
||||
import {MeshLambertMaterial, Object3D} from 'three';
|
||||
import {AXIS} from '../../../math/l3space';
|
||||
import {MeshArrow} from 'scene/objects/auxiliary';
|
||||
import {OnTopOfAll} from 'scene/materialMixins';
|
||||
import DPR from 'dpr';
|
||||
import {viewScaleFactor} from '../../../../../modules/scene/scaleHelper';
|
||||
|
||||
export default class CSysObject3D extends Object3D {
|
||||
|
||||
|
|
@ -41,7 +37,7 @@ export default class CSysObject3D extends Object3D {
|
|||
updateMatrix() {
|
||||
let {origin: o, x, y, z} = this.csys;
|
||||
|
||||
let k = this.viewScaleFactor();
|
||||
let k = viewScaleFactor(this.sceneSetup, this.csys.origin, SIZE_PX, CSYS_SIZE_MODEL);
|
||||
this.matrix.set(
|
||||
k*x.x, k*y.x, k*z.x, o.x,
|
||||
k*x.y, k*y.y, k*z.y, o.y,
|
||||
|
|
@ -52,26 +48,6 @@ export default class CSysObject3D extends Object3D {
|
|||
// this.scale.set(k, k, k);
|
||||
// super.updateMatrix();
|
||||
}
|
||||
|
||||
viewScaleFactor() {
|
||||
let container = this.sceneSetup.container;
|
||||
let viewHeight = container.clientHeight;
|
||||
let camera = this.sceneSetup.camera;
|
||||
|
||||
if (camera.isOrthographicCamera) {
|
||||
return viewHeight / (camera.top - camera.bottom) / camera.zoom * 2 * DPR * SIZE_PX / CSYS_SIZE_MODEL;
|
||||
} else {
|
||||
let p = new Vector3().copy(this.csys.origin);
|
||||
let cp = new Vector3().copy(camera.position);
|
||||
let z = p.sub(cp).length();
|
||||
let tanHFov = Math.atan((camera.fov / 2) / 180 * Math.PI);
|
||||
let fitUnits = tanHFov * z * 2;
|
||||
|
||||
let modelTakingPart = CSYS_SIZE_MODEL / fitUnits;
|
||||
let modelActualSizePx = viewHeight * modelTakingPart;
|
||||
return SIZE_PX / modelActualSizePx;
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.xAxis.dispose();
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import {View} from './view';
|
||||
import * as vec from '../../../math/vec';
|
||||
import {setAttribute} from '../../../../../modules/scene/objectData';
|
||||
import {perpendicularVector} from '../../../math/math';
|
||||
import * as SceneGraph from '../../../../../modules/scene/sceneGraph';
|
||||
import {EDGE} from '../entites';
|
||||
import ScalableLine from '../../../../../modules/scene/objects/scalableLine';
|
||||
|
||||
export class EdgeView extends View {
|
||||
|
||||
|
|
@ -11,99 +10,33 @@ export class EdgeView extends View {
|
|||
super(edge);
|
||||
this.rootGroup = SceneGraph.createGroup();
|
||||
|
||||
const doEdge = (edge, aux, width, color, opacity) => {
|
||||
const geometry = new THREE.Geometry();
|
||||
const scaleTargets = [];
|
||||
geometry.dynamic = true;
|
||||
let materialParams = {
|
||||
color,
|
||||
vertexColors: THREE.FaceColors,
|
||||
shininess: 0,
|
||||
visible: !aux,
|
||||
morphTargets: true
|
||||
};
|
||||
if (opacity !== undefined) {
|
||||
materialParams.transparent = true;
|
||||
materialParams.opacity = opacity;
|
||||
}
|
||||
let tess = edge.data.tesselation ? edge.data.tesselation : edge.curve.tessellateToData();
|
||||
let base = null;
|
||||
for (let i = 1; i < tess.length; i++) {
|
||||
|
||||
let a = tess[i - 1];
|
||||
let b = tess[i];
|
||||
let ab = vec._normalize(vec.sub(b, a));
|
||||
|
||||
let dirs = [];
|
||||
dirs[0] = perpendicularVector(ab);
|
||||
dirs[1] = vec.cross(ab, dirs[0]);
|
||||
dirs[2] = vec.negate(dirs[0]);
|
||||
dirs[3] = vec.negate(dirs[1]);
|
||||
|
||||
dirs.forEach(d => vec._mul(d, width));
|
||||
if (base === null) {
|
||||
base = dirs.map(d => vec.add(a, d));
|
||||
}
|
||||
let lid = dirs.map(d => vec.add(b, d));
|
||||
|
||||
let off = geometry.vertices.length;
|
||||
base.forEach(p => geometry.vertices.push(vThree(p)));
|
||||
lid.forEach(p => geometry.vertices.push(vThree(p)));
|
||||
|
||||
function addScaleTargets(points, origin) {
|
||||
points.forEach(p => scaleTargets.push(vThree(vec._add(vec._mul(vec.sub(p, origin), 10), origin))));
|
||||
}
|
||||
addScaleTargets(base, a);
|
||||
addScaleTargets(lid, b);
|
||||
|
||||
|
||||
base = lid;
|
||||
|
||||
[
|
||||
[0, 4, 3],
|
||||
[3, 4, 7],
|
||||
[2, 3, 7],
|
||||
[7, 6, 2],
|
||||
[0, 1, 5],
|
||||
[5, 4, 0],
|
||||
[1, 2, 6],
|
||||
[6, 5, 1],
|
||||
].forEach(([a, b, c]) => geometry.faces.push(new THREE.Face3(a + off, b + off, c + off)));
|
||||
}
|
||||
geometry.morphTargets.push( { name: "scaleTargets", vertices: scaleTargets } );
|
||||
geometry.computeFaceNormals();
|
||||
|
||||
let mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial(materialParams));
|
||||
this.rootGroup.add(mesh);
|
||||
|
||||
// mesh.morphTargetInfluences[ 0 ] = 0.2;
|
||||
return mesh;
|
||||
};
|
||||
let brepEdge = edge.brepEdge;
|
||||
let tesselation = brepEdge.data.tesselation ? brepEdge.data.tesselation : brepEdge.curve.tessellateToData();
|
||||
this.representation = new ScalableLine(tesselation, 1, 0x2B3856, undefined, false, true);
|
||||
this.marker = new ScalableLine(tesselation, 2, 0xd1726c, undefined, false, true);
|
||||
this.picker = new ScalableLine(tesselation, 10, 0xFA8072, undefined, false, true);
|
||||
this.marker.visible = false;
|
||||
this.picker.material.visible = false;
|
||||
|
||||
this.representation = doEdge(edge.brepEdge, false, 1, 0x2B3856);
|
||||
this.marker = doEdge(edge.brepEdge, true, 3, 0xFA8072, 0.8);
|
||||
|
||||
setAttribute(this.representation, EDGE, this);
|
||||
setAttribute(this.marker, EDGE, this);
|
||||
setAttribute(this.picker, EDGE, this);
|
||||
|
||||
this.rootGroup.add(this.representation);
|
||||
this.rootGroup.add(this.marker);
|
||||
this.rootGroup.add(this.picker);
|
||||
}
|
||||
|
||||
mark(color) {
|
||||
this.marker.material.visible = true;
|
||||
this.marker.visible = true;
|
||||
}
|
||||
|
||||
withdraw(color) {
|
||||
this.marker.material.visible = false;
|
||||
this.marker.visible = false;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.representation.geometry.dispose();
|
||||
this.representation.material.dispose();
|
||||
|
||||
this.marker.geometry.dispose();
|
||||
this.marker.material.dispose();
|
||||
|
||||
this.representation.dispose();
|
||||
this.marker.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
const vThree = arr => new THREE.Vector3().fromArray(arr);
|
||||
|
|
|
|||
|
|
@ -240,9 +240,7 @@ export function makeAngle0_360(angle) {
|
|||
|
||||
export function perpendicularVector(v) {
|
||||
v = vec.normalize(v);
|
||||
return [[1,0,0], [0,1,0], [0,0,1]]
|
||||
.map(axis => vec.cross(axis, v))
|
||||
.sort((a, b) => vec.lengthSq(b) - vec.lengthSq(a))[0];
|
||||
return vec.BASIS3.map(axis => vec.cross(axis, v)).sort((a, b) => vec.lengthSq(b) - vec.lengthSq(a))[0];
|
||||
}
|
||||
|
||||
export function radiusOfCurvature(d1, d2) {
|
||||
|
|
|
|||
|
|
@ -172,3 +172,9 @@ export function polynomial(coefs, vectors) {
|
|||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export const AXIS_X3 = [1,0,0];
|
||||
export const AXIS_Y3 = [0,1,0];
|
||||
export const AXIS_Z3 = [0,0,1];
|
||||
export const ORIGIN3 = [0,0,0];
|
||||
export const BASIS3 = [AXIS_X3, AXIS_Y3, AXIS_Z3];
|
||||
|
|
|
|||
Loading…
Reference in a new issue