diff --git a/src/index.js b/src/index.js index 12a80ff..6ed7e0d 100644 --- a/src/index.js +++ b/src/index.js @@ -47,7 +47,7 @@ function main() { const canvas = document.querySelector('#c'); const view1Elem = document.querySelector('#view1'); - const view2Elem = document.querySelector('#view2'); + // const view2Elem = document.querySelector('#view2'); const renderer = new THREE.WebGLRenderer({ canvas }); const scene = new THREE.Scene(); @@ -68,8 +68,8 @@ function main() { // const controls = new TrackballControls(camera, view1Elem); controls.target.set(0, 0, 0); controls.update() - const cameraHelper = new THREE.CameraHelper(camera); - helpersGroup.add(cameraHelper); + // const cameraHelper = new THREE.CameraHelper(camera); + // helpersGroup.add(cameraHelper); // const camera2 = new THREE.PerspectiveCamera( @@ -78,6 +78,13 @@ function main() { // 0.1, // near // 500, // far // ); + + // const c2size = 10; + // const c2near = 5; + // const c2far = 200; + // const camera2 = new THREE.OrthographicCamera(-c2size, c2size, c2size, -c2size, c2near, c2far); + // camera2.zoom = 0.5 + // camera2.position.set(16, 28, 40); // camera2.lookAt(0, 5, 0); // const controls2 = new OrbitControls(camera2, view2Elem); @@ -94,20 +101,30 @@ function main() { window.sketcher = sketcher scene.add(sketcher) + + const color = 0xFFFFFF; + const intensity = 1; + + const light1 = new THREE.DirectionalLight(color, intensity); + light1.position.set(10, 10, 10); + scene.add(light1); + // const lightHelper = new THREE.DirectionalLightHelper(light1, 5); + // helpersGroup.add(lightHelper); + + const light2 = new THREE.DirectionalLight(color, intensity); + light2.position.set(-10, -10, -5); + scene.add(light2); + // const lightHelper2 = new THREE.DirectionalLightHelper(light2, 5); + // helpersGroup.add(lightHelper2); + { const color = 0xFFFFFF; const intensity = 1; - const light = new THREE.DirectionalLight(color, intensity); - light.position.set(0, 10, 0); - light.target.position.set(-5, 0, 0); + const light = new THREE.AmbientLight(color, intensity); scene.add(light); - scene.add(light.target); } - - - function resizeRendererToDisplaySize(renderer) { const canvas = renderer.domElement; const width = canvas.clientWidth; @@ -151,8 +168,12 @@ function main() { camera.left = -aspect; camera.right = aspect; camera.updateProjectionMatrix(); - cameraHelper.update(); - cameraHelper.visible = false; + // cameraHelper.update(); + // cameraHelper.visible = false; + // lightHelper.visible = false; + // lightHelper2.visible = false; + + scene.background.set(0xb0b0b0); renderer.render(scene, camera); } // { @@ -160,6 +181,9 @@ function main() { // camera2.aspect = aspect; // camera2.updateProjectionMatrix(); // cameraHelper.visible = true; + // lightHelper.visible = true; + // lightHelper2.visible = true; + // scene.background.set(0x000040); // renderer.render(scene, camera2); // } diff --git a/src/sketcher/Sketcher.js b/src/sketcher/Sketcher.js index 20a5329..d3a395f 100644 --- a/src/sketcher/Sketcher.js +++ b/src/sketcher/Sketcher.js @@ -3,8 +3,11 @@ import { Matrix4 } from 'three'; import * as THREE from 'three/src/Three' -import { onClick_1, onClick_2, beforeClick_2, clear } from './drawEvents' +import { drawOnClick1, drawOnClick2, drawPreClick2, drawClear } from './drawEvents' import { onHover, onDrag, onPick, onRelease } from './pickEvents' +import { addDimension, setCoincident } from './constraintEvents' +import { get3PtArc } from './sketchArc' +import { extrude } from './extrude' const lineMaterial = new THREE.LineBasicMaterial({ @@ -12,6 +15,7 @@ const lineMaterial = new THREE.LineBasicMaterial({ color: 0x555555, }) + const pointMaterial = new THREE.PointsMaterial({ color: 0x555555, size: 4, @@ -31,7 +35,7 @@ class Sketcher extends THREE.Group { this.raycaster = new THREE.Raycaster(); this.raycaster.params.Line.threshold = 0.4; - this.raycaster.params.Points.threshold = 0.4; + this.raycaster.params.Points.threshold = 2; // [0]:x, [1]:y, [2]:z this.objIdx = new Map() @@ -55,13 +59,13 @@ class Sketcher extends THREE.Group { this.constraintsBuf = new Float32Array(this.max_constraints * 6).fill(NaN) this.contraintNum = { 'coincident': 0, - 'parallel': 1 + 'distance': 1 } - this.onClick_1 = onClick_1.bind(this); - this.beforeClick_2 = beforeClick_2.bind(this); - this.onClick_2 = onClick_2.bind(this); + this.drawOnClick1 = drawOnClick1.bind(this); + this.drawPreClick2 = drawPreClick2.bind(this); + this.drawOnClick2 = drawOnClick2.bind(this); this.onHover = onHover.bind(this); this.onPick = onPick.bind(this); @@ -77,6 +81,7 @@ class Sketcher extends THREE.Group { this.mode = "" this.subsequent = false; this.selected = new Set() + this.hovered = [] this.target = new THREE.Vector3(); } @@ -94,23 +99,32 @@ class Sketcher extends THREE.Group { onKeyPress(e) { switch (e.key) { case 'Escape': - clear.bind(this)() + drawClear.bind(this)() this.mode = "" break; case 'l': if (this.mode == 'line') { - this.clear() + drawClear.bind(this)() } - this.domElement.addEventListener('pointerdown', this.onClick_1) + this.domElement.addEventListener('pointerdown', this.drawOnClick1) this.mode = "line" break; case 'a': - this.domElement.addEventListener('pointerdown', this.onClick_1) + this.domElement.addEventListener('pointerdown', this.drawOnClick1) this.mode = "arc" break; - case 'd': + case 'x': this.deleteSelected() break; + case 'c': + setCoincident.call(this) + this.updateOtherBuffers() + this.solve() + this.mode = "" + break; + case 'e': + extrude.call(this) + break; case '=': this.plane.applyMatrix4(new Matrix4().makeRotationY(0.1)) this.orientSketcher() @@ -133,7 +147,8 @@ class Sketcher extends THREE.Group { minI = Math.min(minI, this.delete(obj)) } - this.updatePointsBuffer(minI) + // this.updatePointsBuffer(minI) + this.updatePointsBuffer() this.updateOtherBuffers() this.selected.clear() @@ -265,12 +280,27 @@ class Sketcher extends THREE.Group { pos.needsUpdate = true; } - - this.dispatchEvent({ type: 'change' }) - Module._free(pts_buffer) Module._free(links_buffer) Module._free(constraints_buffer) + + + for (let [k, obj] of this.linkedObjs) { + if (obj[0] == 'line') continue; + const [p1, p2, c, arc] = obj[1] + + const points = get3PtArc( + p1.geometry.attributes.position.array, + p2.geometry.attributes.position.array, + c.geometry.attributes.position.array + ); + + arc.geometry.attributes.position.set(points) + arc.needsUpdate = true; + } + + + this.dispatchEvent({ type: 'change' }) } } diff --git a/src/sketcher/constraintEvents.js b/src/sketcher/constraintEvents.js new file mode 100644 index 0000000..7dce25d --- /dev/null +++ b/src/sketcher/constraintEvents.js @@ -0,0 +1,80 @@ +import * as THREE from 'three/src/Three' + +import { lineMaterial, pointMaterial } from './Sketcher' + +export function addDimension(ent1, ent2, distance) { + + + // if (ent1.type ==) + + this.constraints.set(++this.c_id, + [ + 'distance', distance, + [p1, p2, -1, -1] + ] + ) + + ent1.constraints.add(this.c_id) + ent2.constraints.add(this.c_id) + +} + + + +// function findTouching(node) { +// const res = [node] +// for (let t of node.constraints) { +// if (this.constraints.get(t)[0] != 'coincident') continue +// for (let i = 0; i < 2; i++) { +// let d = this.constraints.get(t)[2][i] +// if (d != node) res.push(d) +// } +// } +// return res +// } + + +// export function setCoincident(ent1, ent2) { + +// for (let n1 of findTouching.call(this,ent1)) { +// for (let n2 of findTouching.call(this,ent2)) { +// this.constraints.set(++this.c_id, +// [ +// 'coincident', -1, +// [n1, n2, -1, -1] +// ] +// ) +// n1.constraints.add(this.c_id) +// n2.constraints.add(this.c_id) +// } +// } + +// } + + + + + +export function setCoincident() { + const s = new Set() + const toComb = [] + for (let node of this.selected) { + const xc = node.geometry.attributes.position.array[0] + if (!s.has(xc)) { + toComb.push(node) + s.add(xc) + } + } + + for (let i = 1; i < toComb.length; i++) { + this.constraints.set(++this.c_id, + [ + 'coincident', -1, + [toComb[i - 1], toComb[i], -1, -1] + ] + ) + toComb[i].constraints.add(this.c_id) + toComb[i - 1].constraints.add(this.c_id) + } + +} diff --git a/src/sketcher/drawEvents.js b/src/sketcher/drawEvents.js index 3c8895d..20d3e82 100644 --- a/src/sketcher/drawEvents.js +++ b/src/sketcher/drawEvents.js @@ -2,9 +2,9 @@ import { sketchArc, sketchArc2 } from './sketchArc' import { sketchLine, sketchLine2 } from './sketchLine' -export function onClick_1(e) { +export function drawOnClick1(e) { if (e.buttons !== 1) return - this.domElement.removeEventListener('pointerdown', this.onClick_1) + this.domElement.removeEventListener('pointerdown', this.drawOnClick1) const mouseLoc = this.getLocation(e); if (this.mode == "line") { @@ -22,12 +22,12 @@ export function onClick_1(e) { } this.l_id += 1 - this.domElement.addEventListener('pointermove', this.beforeClick_2) - this.domElement.addEventListener('pointerdown', this.onClick_2) + this.domElement.addEventListener('pointermove', this.drawPreClick2) + this.domElement.addEventListener('pointerdown', this.drawOnClick2) } -export function beforeClick_2(e) { +export function drawPreClick2(e) { const mouseLoc = this.getLocation(e); if (this.mode == "line") { @@ -39,10 +39,10 @@ export function beforeClick_2(e) { this.dispatchEvent({ type: 'change' }) } -export function onClick_2(e) { +export function drawOnClick2(e) { if (e.buttons !== 1) return; - this.domElement.removeEventListener('pointermove', this.beforeClick_2); - this.domElement.removeEventListener('pointerdown', this.onClick_2); + this.domElement.removeEventListener('pointermove', this.drawPreClick2); + this.domElement.removeEventListener('pointerdown', this.drawOnClick2); this.updatePointsBuffer(this.updatePoint) this.updateOtherBuffers() @@ -50,20 +50,20 @@ export function onClick_2(e) { if (this.mode == "line") { this.subsequent = true - this.onClick_1(e) + this.drawOnClick1(e) } else if (this.mode == "arc") { // this.domElement.addEventListener('pointermove', this.beforeClick_3) } } -export function clear() { +export function drawClear() { if (this.mode == "") return if (this.mode == "line") { - this.domElement.removeEventListener('pointerdown', this.onClick_1) - this.domElement.removeEventListener('pointermove', this.beforeClick_2); - this.domElement.removeEventListener('pointerdown', this.onClick_2); + this.domElement.removeEventListener('pointerdown', this.drawOnClick1) + this.domElement.removeEventListener('pointermove', this.drawPreClick2); + this.domElement.removeEventListener('pointerdown', this.drawOnClick2); this.delete(this.children[this.children.length - 1]) diff --git a/src/sketcher/extrude.js b/src/sketcher/extrude.js new file mode 100644 index 0000000..3b98ccc --- /dev/null +++ b/src/sketcher/extrude.js @@ -0,0 +1,72 @@ +import * as THREE from 'three/src/Three' + + +export function extrude() { + + + + let constraints = this.constraints; + let linkedObjs = this.linkedObjs; + let children = this.children; + let visited = new Set() + let v2s = [] + + function findPair(node) { + visited.add(node) + let linkedObj = linkedObjs.get(node.l_id) + console.log(linkedObj) + let arr; + if (linkedObj[0] == 'line') { + arr = linkedObj[1][2].geometry.attributes.position.array + } else if (linkedObj[0] == 'arc') { + arr = linkedObj[1][3].geometry.attributes.position.array + } + for (let i = 0; i < arr.length; i += 3) { + v2s.push(new THREE.Vector2(arr[i], arr[i + 1])) + } + + for (let i = 0; i < 2; i++) { + let d = linkedObj[1][i] + if (d == -1 || d == node) continue; + if (d == children[1]) { + console.log('pair found') + }; + findTouching(d) + } + + } + + + function findTouching(node) { + for (let t of node.constraints) { + if (constraints.get(t)[0] != 'coincident') continue + for (let d of constraints.get(t)[2]) { + if (d == -1 || d == node) continue; + if (d == children[1]) { + console.log('touching found') + } else { + if (!visited.has(d)) { + findPair(d) + } + }; + } + } + } + + + findPair(children[1]) + + const shape = new THREE.Shape(v2s); + + const extrudeSettings = { amount: 8, bevelEnabled: false}; + + const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings); + + const phong = new THREE.MeshPhongMaterial( { color: 0x156289, emissive: 0x072534, side: THREE.DoubleSide, flatShading: true } ); + const mesh = new THREE.Mesh(geometry, phong); + this.add(mesh) + +} + + + diff --git a/src/sketcher/geometry.js b/src/sketcher/geometry.js index 7737527..8b13789 100644 --- a/src/sketcher/geometry.js +++ b/src/sketcher/geometry.js @@ -1,50 +1 @@ -export function get2PtArc(p1, p2, divisions = 36) { - const dx = p2[0] - p1[0] - const dy = p2[1] - p1[1] - const dist = Math.sqrt(dx ** 2 + dy ** 2) - const midAngle = (Math.atan2(dy, dx) - Math.PI / 2) % (2 * Math.PI) - let a1 = midAngle - Math.PI / 6 - let a2 = midAngle + Math.PI / 6 - - a1 = a1 < 0 ? a1 + 2 * Math.PI : a1 - a2 = a2 < 0 ? a2 + 2 * Math.PI : a1 - - const factor = Math.tan(Math.PI / 3) - const cx = (p1[0] + p2[0] - dy * factor) / 2 - const cy = (p1[1] + p2[1] + dx * factor) / 2 - - const radius = dist - const deltaAngle = Math.PI / 3 - let points = new Float32Array((divisions + 1) * 3) - - for (let d = 0; d <= divisions; d++) { - const angle = a1 + (d / divisions) * deltaAngle; - points[3 * d] = cx + radius * Math.cos(angle); - points[3 * d + 1] = cy + radius * Math.sin(angle); - } - return [points, [cx, cy]]; -} - - -export function get3PtArc(p1, p2, c, divisions = 36) { - - const v1 = [p1[0] - c[0], p1[1] - c[1]] - const v2 = [p2[0] - c[0], p2[1] - c[1]] - - let a1 = Math.atan2(v1[1], v1[0]) - let a2 = Math.atan2(v2[1], v2[0]) - - const radius = Math.sqrt(v1[0] ** 2 + v1[1] ** 2) - - const deltaAngle = a2 - a1 - - let points = new Float32Array((divisions + 1) * 3) - - for (let d = 0; d <= divisions; d++) { - const angle = a1 + (d / divisions) * deltaAngle; - points[3 * d] = c[0] + radius * Math.cos(angle); - points[3 * d + 1] = c[1] + radius * Math.sin(angle); - } - return points; -} diff --git a/src/sketcher/pickEvents.js b/src/sketcher/pickEvents.js index 3feb7dc..4daa74d 100644 --- a/src/sketcher/pickEvents.js +++ b/src/sketcher/pickEvents.js @@ -2,8 +2,15 @@ import * as THREE from 'three/src/Three' export function onHover(e) { if (this.mode || e.buttons) return - if (this.hovered && !this.selected.has(this.hovered)) { - this.hovered.material.color.set(0x555555) + + if (this.hovered.length) { + for (let ob of this.hovered) { + if (ob && !this.selected.has(ob)) { + ob.material.color.set(0x555555) + } + } + this.hovered = [] + this.dispatchEvent({ type: 'change' }) } this.raycaster.setFromCamera( @@ -15,42 +22,46 @@ export function onHover(e) { ); const hoverPts = this.raycaster.intersectObjects(this.children) - // console.log(hoverPts) + // console.log(hoverPts) if (hoverPts.length) { let minDist = Infinity; - let idx = 0 + let idx = [] for (let i = 0; i < hoverPts.length; i++) { - if (hoverPts[i].distanceToRay && hoverPts[i].distanceToRay <= minDist) { + if (!hoverPts[i].distanceToRay) continue; + if (hoverPts[i].distanceToRay < minDist) { minDist = hoverPts[i].distanceToRay - idx = i + idx = [i] + } else if (hoverPts[i].distanceToRay == minDist) { + idx.push(i) } } - hoverPts[idx].object.material.color.set(0xff0000) - this.hovered = hoverPts[idx].object + + // if (!idx.length) idx.push(0) + + for (let i of idx) { + hoverPts[i].object.material.color.set(0x00ff00) + // hoverPts[i].object.material.color.set(0xff0000) + this.hovered.push(hoverPts[i].object) + } this.dispatchEvent({ type: 'change' }) return } - - if (this.hovered) { - this.hovered = null; - this.dispatchEvent({ type: 'change' }) - } - } export function onPick(e) { if (this.mode || e.buttons != 1) return - if (this.hovered) { - this.selected.add(this.hovered) - if (this.hovered.type === "Points") { - this.grabPtIdx = this.children.indexOf( - this.hovered - ) + if (this.hovered.length) { + + for (let h of this.hovered) { + this.selected.add(h) + } + + if (this.hovered[0].type == "Points") { this.domElement.addEventListener('pointermove', this.onDrag); this.domElement.addEventListener('pointerup', this.onRelease) } @@ -66,10 +77,12 @@ export function onPick(e) { export function onDrag(e) { const mouseLoc = this.getLocation(e); - this.ptsBuf.set( - mouseLoc, - this.objIdx.get(this.children[this.grabPtIdx].id) * 3 - ) + for (let h of this.hovered) { + this.ptsBuf.set( + mouseLoc, + this.objIdx.get(h.id) * 3 + ) + } this.solve() this.dispatchEvent({ type: 'change' }) @@ -79,6 +92,10 @@ export function onDrag(e) { export function onRelease() { this.domElement.removeEventListener('pointermove', this.onDrag) this.domElement.removeEventListener('pointerup', this.onRelease) - this.children[this.grabPtIdx].geometry.computeBoundingSphere() + + for (let ii of this.hovered) { + ii.geometry.computeBoundingSphere() + } + } diff --git a/src/sketcher/sketchArc.js b/src/sketcher/sketchArc.js index 07d049a..51d7dd7 100644 --- a/src/sketcher/sketchArc.js +++ b/src/sketcher/sketchArc.js @@ -1,6 +1,5 @@ import * as THREE from 'three/src/Three' -import { get2PtArc, get3PtArc } from './geometry' import {lineMaterial, pointMaterial} from './Sketcher' export function sketchArc(mouseLoc) { @@ -60,4 +59,56 @@ export function sketchArc2(mouseLoc, toPush) { p3.geometry.attributes.position.set(center); p3.geometry.attributes.position.needsUpdate = true; p3.geometry.computeBoundingSphere() +} + +export function get2PtArc(p1, p2, divisions = 36) { + + const dx = p2[0] - p1[0] + const dy = p2[1] - p1[1] + const dist = Math.sqrt(dx ** 2 + dy ** 2) + const midAngle = (Math.atan2(dy, dx) - Math.PI / 2) % (2 * Math.PI) + let a1 = midAngle - Math.PI / 6 + let a2 = midAngle + Math.PI / 6 + + a1 = a1 < 0 ? a1 + 2 * Math.PI : a1 + a2 = a2 < 0 ? a2 + 2 * Math.PI : a1 + + const factor = Math.tan(Math.PI / 3) + const cx = (p1[0] + p2[0] - dy * factor) / 2 + const cy = (p1[1] + p2[1] + dx * factor) / 2 + + const radius = dist + const deltaAngle = Math.PI / 3 + let points = new Float32Array((divisions + 1) * 3) + + for (let d = 0; d <= divisions; d++) { + const angle = a1 + (d / divisions) * deltaAngle; + points[3 * d] = cx + radius * Math.cos(angle); + points[3 * d + 1] = cy + radius * Math.sin(angle); + } + return [points, [cx, cy]]; +} + + +export function get3PtArc(p1, p2, c, divisions = 36) { + + const v1 = [p1[0] - c[0], p1[1] - c[1]] + const v2 = [p2[0] - c[0], p2[1] - c[1]] + + let a1 = Math.atan2(v1[1], v1[0]) + let a2 = Math.atan2(v2[1], v2[0]) + + const radius = Math.sqrt(v1[0] ** 2 + v1[1] ** 2) + + let deltaAngle = a2 - a1 + if (deltaAngle <0) deltaAngle += Math.PI*2 + // console.log(deltaAngle) + let points = new Float32Array((divisions + 1) * 3) + + for (let d = 0; d <= divisions; d++) { + const angle = a1 + (d / divisions) * deltaAngle; + points[3 * d] = c[0] + radius * Math.cos(angle); + points[3 * d + 1] = c[1] + radius * Math.sin(angle); + } + return points; } \ No newline at end of file diff --git a/src/sketcher/sketchLine.js b/src/sketcher/sketchLine.js index 1371911..cd6d209 100644 --- a/src/sketcher/sketchLine.js +++ b/src/sketcher/sketchLine.js @@ -39,7 +39,7 @@ export function sketchLine(mouseLoc) { if (this.subsequent) { - this.constraints.set(this.c_id, + this.constraints.set(++this.c_id, [ 'coincident', -1, [this.children[this.children.length - 2], p1, -1, -1] @@ -48,7 +48,6 @@ export function sketchLine(mouseLoc) { p1.constraints.add(this.c_id) this.children[this.children.length - 2].constraints.add(this.c_id) - this.c_id += 1 }