diff --git a/modules/math/vectorAdapters.js b/modules/math/vectorAdapters.js new file mode 100644 index 00000000..b6564d36 --- /dev/null +++ b/modules/math/vectorAdapters.js @@ -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); + diff --git a/modules/scene/objects/scalableLine.js b/modules/scene/objects/scalableLine.js new file mode 100644 index 00000000..c4bf70d3 --- /dev/null +++ b/modules/scene/objects/scalableLine.js @@ -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; \ No newline at end of file diff --git a/modules/scene/scaleHelper.js b/modules/scene/scaleHelper.js new file mode 100644 index 00000000..88f1a44f --- /dev/null +++ b/modules/scene/scaleHelper.js @@ -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; + } +} \ No newline at end of file diff --git a/modules/scene/sceneSetup.js b/modules/scene/sceneSetup.js index 3dca4017..d21436d4 100644 --- a/modules/scene/sceneSetup.js +++ b/modules/scene/sceneSetup.js @@ -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; \ No newline at end of file diff --git a/modules/scene/utils/calcFaceNormal.js b/modules/scene/utils/calcFaceNormal.js new file mode 100644 index 00000000..4fd7c91b --- /dev/null +++ b/modules/scene/utils/calcFaceNormal.js @@ -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(); +} \ No newline at end of file diff --git a/web/app/brep/geom/curves/frenetFrame.js b/web/app/brep/geom/curves/frenetFrame.js new file mode 100644 index 00000000..6227f02c --- /dev/null +++ b/web/app/brep/geom/curves/frenetFrame.js @@ -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]; +} \ No newline at end of file diff --git a/web/app/cad/craft/datum/csysObject.js b/web/app/cad/craft/datum/csysObject.js index f42acfae..eff38ba6 100644 --- a/web/app/cad/craft/datum/csysObject.js +++ b/web/app/cad/craft/datum/csysObject.js @@ -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(); diff --git a/web/app/cad/scene/views/edgeView.js b/web/app/cad/scene/views/edgeView.js index 95ddef44..b904b172 100644 --- a/web/app/cad/scene/views/edgeView.js +++ b/web/app/cad/scene/views/edgeView.js @@ -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); diff --git a/web/app/math/math.js b/web/app/math/math.js index e43adae8..96e5beb3 100644 --- a/web/app/math/math.js +++ b/web/app/math/math.js @@ -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) { diff --git a/web/app/math/vec.js b/web/app/math/vec.js index 573fb423..e70639e3 100644 --- a/web/app/math/vec.js +++ b/web/app/math/vec.js @@ -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];