From 23da7dcf83b2643b515d78d51537fc7afdfadd62 Mon Sep 17 00:00:00 2001 From: howard Date: Thu, 25 Mar 2021 01:04:13 -0700 Subject: [PATCH] refactor --- src/Sketcher.js | 561 ------------------------------------- src/index.js | 2 +- src/scratch.js | 1 - src/sketchTools.js | 56 ---- src/sketcher/Sketcher.js | 281 +++++++++++++++++++ src/sketcher/drawEvents.js | 73 +++++ src/sketcher/geometry.js | 50 ++++ src/sketcher/pickEvents.js | 84 ++++++ src/sketcher/sketchArc.js | 63 +++++ src/sketcher/sketchLine.js | 70 +++++ src/utils.js | 10 - wasm/CDemo.c | 269 ++++++++++++++++++ wasm/DOC.txt | 467 ++++++++++++++++++++++++++++++ wasm/solver.c | 37 ++- 14 files changed, 1382 insertions(+), 642 deletions(-) delete mode 100644 src/Sketcher.js delete mode 100644 src/scratch.js delete mode 100644 src/sketchTools.js create mode 100644 src/sketcher/Sketcher.js create mode 100644 src/sketcher/drawEvents.js create mode 100644 src/sketcher/geometry.js create mode 100644 src/sketcher/pickEvents.js create mode 100644 src/sketcher/sketchArc.js create mode 100644 src/sketcher/sketchLine.js create mode 100644 wasm/CDemo.c create mode 100644 wasm/DOC.txt diff --git a/src/Sketcher.js b/src/Sketcher.js deleted file mode 100644 index d2f0472..0000000 --- a/src/Sketcher.js +++ /dev/null @@ -1,561 +0,0 @@ - -import { Matrix4 } from 'three'; - -import * as THREE from '../node_modules/three/src/Three' - -import { sketchLine } from './sketchTools' - - -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]]; -} - - -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; -} - - -export class Sketcher extends THREE.Group { - constructor(camera, domElement, plane) { - super() - this.camera = camera; - this.domElement = domElement; - this.plane = plane; - this.matrixAutoUpdate = false; - this.sketchNormal = new THREE.Vector3(0, 0, 1) - this.orientSketcher(plane) - - this.add(new THREE.PlaneHelper(this.plane, 1, 0xffff00)); - - this.colorPt = new THREE.Color('white') - this.selected = new Set() - - - this.lineMaterial = new THREE.LineBasicMaterial({ - linewidth: 3, - color: 0x555555, - }) - this.pointMaterial = new THREE.PointsMaterial({ - color: 0x555555, - size: 3, - }) - - - this.onKeyPress = this.onKeyPress.bind(this); - - this.onClick_1 = this.onClick_1.bind(this); - this.onClick_2 = this.onClick_2.bind(this); - this.beforeClick_2 = this.beforeClick_2.bind(this); - this.beforeClick_3 = this.beforeClick_3.bind(this); - this.onPick = this.onPick.bind(this); - this.onHover = this.onHover.bind(this); - this.onDrag = this.onDrag.bind(this); - this.onRelease = this.onRelease.bind(this); - - this.raycaster = new THREE.Raycaster(); - this.raycaster.params.Line.threshold = 0.4; - this.raycaster.params.Points.threshold = 0.2; - - - window.addEventListener('keydown', this.onKeyPress) - domElement.addEventListener('pointerdown', this.onPick) - domElement.addEventListener('pointermove', this.onHover) - - - this.mode = "" - - this.linkedObjs = new Map() - this.l_id = 0; - - this.constraints = new Map() - this.c_id = 0; - - this.objIdx = new Map() - - this.max_pts = 1000 - this.buffer = new ArrayBuffer(3*4*this.max_pts); - this.ptsBuf = new Float32Array(this.buffer).fill(NaN) - - this.max_links = 1000 - - // [0]:type, [1]:pt1, [2]:pt2, [3]:pt3, [4]:pt4 - this.linksBuf = new Float32Array(this.max_links * 5).fill(NaN) - - this.max_constraints = 1000 - - // [0]:type, [1]:val, [2]:pt1, [3]:pt2, [4]:lk1, [5]:lk2 - this.constraintsBuf = new Float32Array(this.max_constraints * 6).fill(NaN) - - this.subsequent = false; - this.ptsBufPt = 0; - this.endBufPt = 0; - - this.linkNum = { - 'line': 0, - 'arc': 1 - } - - this.contraintNum = { - 'coincident': 0, - 'parallel': 1 - } - } - - orientSketcher() { - - const theta = this.sketchNormal.angleTo(this.plane.normal) - const axis = this.sketchNormal.clone().cross(this.plane.normal).normalize() - const rot = new THREE.Matrix4().makeRotationAxis(axis, theta) - const trans = new THREE.Matrix4().makeTranslation(0, 0, this.plane.constant) - - this.matrix = rot.multiply(trans) // world matrix will auto update in next render - this.inverse = this.matrix.clone().invert() - - } - - onKeyPress(e) { - switch (e.key) { - case 'Escape': - this.clear() - this.mode = "" - break; - case 'l': - if (this.mode == 'line') { - this.clear() - } - this.domElement.addEventListener('pointerdown', this.onClick_1) - this.mode = "line" - break; - case 'a': - this.domElement.addEventListener('pointerdown', this.onClick_1) - this.mode = "arc" - break; - case 'd': - this.deleteSelected() - break; - case '=': - this.plane.applyMatrix4(new Matrix4().makeRotationY(0.1)) - this.orientSketcher() - this.dispatchEvent({ type: 'change' }) - break; - case '-': - this.plane.applyMatrix4(new Matrix4().makeRotationY(-0.1)) - this.orientSketcher() - this.dispatchEvent({ type: 'change' }) - break; - } - } - - - onHover(e) { - if (this.mode || e.buttons) return - - if (this.hovered && !this.selected.has(this.hovered)) { - this.hovered.material.color.set(0x555555) - } - - this.raycaster.setFromCamera( - new THREE.Vector2( - (e.clientX / window.innerWidth) * 2 - 1, - - (e.clientY / window.innerHeight) * 2 + 1 - ), - this.camera - ); - - const hoverPts = this.raycaster.intersectObjects(this.children) - - if (hoverPts.length) { - let minDist = Infinity; - let idx = 0 - for (let i = 0; i < hoverPts.length; i++) { - if (hoverPts[i].distanceToRay && hoverPts[i].distanceToRay <= minDist) { - minDist = hoverPts[i].distanceToRay - idx = i - } - } - - hoverPts[idx].object.material.color.set(0xff0000) - this.hovered = hoverPts[idx].object - this.dispatchEvent({ type: 'change' }) - return - } - - - if (this.hovered) { - this.hovered = null; - this.dispatchEvent({ type: 'change' }) - } - - } - - - - 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 - ) - this.domElement.addEventListener('pointermove', this.onDrag); - this.domElement.addEventListener('pointerup', this.onRelease) - } - } else { - for (let obj of this.selected) { - obj.material.color.set(0x555555) - } - this.dispatchEvent({ type: 'change' }) - this.selected.clear() - } - } - - onDrag(e) { - const mouseLoc = this.getLocation(e); - - this.ptsBuf.set( - mouseLoc, - this.objIdx.get(this.children[this.grabPtIdx].id) * 3 - ) - - this.solve() - this.dispatchEvent({ type: 'change' }) - } - - - onRelease() { - this.domElement.removeEventListener('pointermove', this.onDrag) - this.domElement.removeEventListener('pointerup', this.onRelease) - this.children[this.grabPtIdx].geometry.computeBoundingSphere() - } - - - - deleteSelected() { - let minI = this.children.length; - - for (let obj of this.selected) { - minI = Math.min(minI, this.delete(obj)) - - } - - this.updatePointsBuffer(minI) - this.updateOtherBuffers() - - this.selected.clear() - this.dispatchEvent({ type: 'change' }) - } - - deleteConstraints(c_id) { - for (let ob of this.constraints.get(c_id)[2]) { - if (ob == -1) continue - ob.constraints.delete(c_id) - } - this.constraints.delete(c_id) - } - - updateOtherBuffers() { - let i = 0 - for (let [key, obj] of this.constraints) { - this.constraintsBuf.set( - [ - this.contraintNum[obj[0]], obj[1], - ...obj[2].map(ele => this.objIdx.get(ele.id) ?? -1), - ], - (i) * 6 - ) - i++ - } - - i = 0; - for (let [key, obj] of this.linkedObjs) { - this.linksBuf.set( - [ - this.linkNum[obj[0]], - ...obj[1].map(ele => this.objIdx.get(ele.id) ?? -1), - ], - (i) * 5 - ) - i++ - } - - } - - delete(obj) { - - const link = this.linkedObjs.get(obj.l_id)[1] - - let i = this.children.indexOf(link[0]) - if (i == -1) return Infinity - - for (let j = 0; j < link.length; j++) { - const obj = this.children[i + j] - obj.geometry.dispose() - obj.material.dispose() - - for (let c_id of obj.constraints) { - this.deleteConstraints(c_id) - } - } - - this.children.splice(i, link.length) - - this.linkedObjs.delete(obj.l_id) - - return i - } - - updatePointsBuffer(startingIdx = 0) { - for (let i = startingIdx; i < this.children.length; i++) { - const obj = this.children[i] - this.objIdx.set(obj.id, i) - if (obj.type == "Points") { - this.ptsBuf[3 * i] = obj.geometry.attributes.position.array[0] - this.ptsBuf[3 * i + 1] = obj.geometry.attributes.position.array[1] - } - } - } - - - clear() { - 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.delete(this.children[this.children.length - 1]) - - this.dispatchEvent({ type: 'change' }) - this.subsequent = false - } - } - - getLocation(e) { - this.raycaster.setFromCamera( - new THREE.Vector2( - (e.clientX / window.innerWidth) * 2 - 1, - - (e.clientY / window.innerHeight) * 2 + 1 - ), - this.camera - ); - // return this.worldToLocal(this.raycaster.ray.intersectPlane(this.plane)).toArray() - return this.raycaster.ray.intersectPlane(this.plane) - .applyMatrix4(this.inverse).toArray() - } - - onClick_1(e) { - if (e.buttons !== 1) return - const mouseLoc = this.getLocation(e); - - - if (this.mode == "line") { - - sketchLine.call(this, mouseLoc) - - } else if (this.mode == "arc") { - this.p1Geom = new THREE.BufferGeometry().setAttribute('position', - new THREE.BufferAttribute(new Float32Array(mouseLoc), 3) - ) - this.p1 = new THREE.Points(this.p1Geom, - new THREE.PointsMaterial().copy(this.pointMaterial) - ); - this.p1.matrixAutoUpdate = false; - this.p1.constraints = new Set() - - this.p2Geom = new THREE.BufferGeometry().setAttribute('position', - new THREE.BufferAttribute(new Float32Array(3), 3) - ) - this.p2 = new THREE.Points( - this.p2Geom, - new THREE.PointsMaterial().copy(this.pointMaterial) - ); - this.p2.matrixAutoUpdate = false; - this.p2.constraints = new Set() - - this.arcGeom = new THREE.BufferGeometry().setAttribute('position', - new THREE.BufferAttribute(new Float32Array(3 * 37), 3) - ) - - this.arc = new THREE.Line(this.arcGeom, - new THREE.LineBasicMaterial().copy(this.lineMaterial) - ); - this.arc.frustumCulled = false; - - this.p3Geom = new THREE.BufferGeometry().setAttribute('position', - new THREE.BufferAttribute(new Float32Array(3), 3) - ) - this.p3 = new THREE.Points(this.p3Geom, - new THREE.PointsMaterial().copy(this.pointMaterial) - ); - - this.toPush = [this.p1, this.p2, this.p3, this.arc] - } - - - this.updatePoint = this.children.length - this.add(...this.toPush) - - this.linkedObjs.set(this.l_id, [this.mode, this.toPush]) - - for (let obj of this.toPush) { - obj.l_id = this.l_id - } - this.l_id += 1 - - - this.domElement.removeEventListener('pointerdown', this.onClick_1) - this.domElement.addEventListener('pointermove', this.beforeClick_2) - this.domElement.addEventListener('pointerdown', this.onClick_2) - } - - - beforeClick_2(e) { - const mouseLoc = this.getLocation(e); - - this.p2Geom.attributes.position.set(mouseLoc); - this.p2Geom.attributes.position.needsUpdate = true; - this.p2Geom.computeBoundingSphere() - - if (this.mode == "line") { - this.lineGeom.attributes.position.set(mouseLoc, 3) - this.lineGeom.attributes.position.needsUpdate = true; - } else if (this.mode == 'arc') { - const [points, center] = get2PtArc( - this.p1Geom.attributes.position.array, - this.p2Geom.attributes.position.array - ) - this.arcGeom.attributes.position.set( - points - ); - this.arcGeom.attributes.position.needsUpdate = true; - this.p3Geom.attributes.position.set(center); - this.p3Geom.attributes.position.needsUpdate = true; - this.p3Geom.computeBoundingSphere() - - } - - this.dispatchEvent({ type: 'change' }) - } - - onClick_2(e) { - if (e.buttons !== 1) return; - this.domElement.removeEventListener('pointermove', this.beforeClick_2); - this.domElement.removeEventListener('pointerdown', this.onClick_2); - - - if (this.mode == "line") { - - - this.updatePointsBuffer(this.updatePoint) - this.updateOtherBuffers() - - - this.subsequent = true - this.onClick_1(e) - } else if (this.mode == "arc") { - // this.domElement.addEventListener('pointermove', this.beforeClick_3) - } - } - - beforeClick_3(e) { - const mouseLoc = this.getLocation(e); - this.p3Geom.attributes.position.set(mouseLoc); - this.p3Geom.attributes.position.needsUpdate = true; - this.p3Geom.computeBoundingSphere() - } - - - solve() { - - const pts_buffer = - Module._malloc(this.ptsBuf.length * this.ptsBuf.BYTES_PER_ELEMENT) - Module.HEAPF32.set(this.ptsBuf, pts_buffer >> 2) - - const constraints_buffer = - Module._malloc(this.constraintsBuf.length * this.constraintsBuf.BYTES_PER_ELEMENT) - Module.HEAPF32.set(this.constraintsBuf, constraints_buffer >> 2) - - const links_buffer = - Module._malloc(this.linksBuf.length * this.linksBuf.BYTES_PER_ELEMENT) - Module.HEAPF32.set(this.linksBuf, links_buffer >> 2) - - Module["_solver"]( - this.children.length, pts_buffer, - this.constraints.size, constraints_buffer, - this.linkedObjs.size, links_buffer) - - let ptr = pts_buffer >> 2; - - - for (let i = 0; i < this.children.length; i += 1) { - - const pos = this.children[i].geometry.attributes.position; - if (isNaN(Module.HEAPF32[ptr])) { - pos.array[0] = Module.HEAPF32[ptr - 6] - pos.array[1] = Module.HEAPF32[ptr - 5] - pos.array[3] = Module.HEAPF32[ptr - 3] - pos.array[4] = Module.HEAPF32[ptr - 2] - } else { - pos.array[0] = Module.HEAPF32[ptr] - pos.array[1] = Module.HEAPF32[ptr + 1] - } - ptr += 3; - pos.needsUpdate = true; - } - - - this.dispatchEvent({ type: 'change' }) - - Module._free(pts_buffer) - } - -} - - - diff --git a/src/index.js b/src/index.js index 03b3d87..12a80ff 100644 --- a/src/index.js +++ b/src/index.js @@ -30,7 +30,7 @@ import * as THREE from '../node_modules/three/src/Three'; import { OrbitControls } from './OrbitControls' import { TrackballControls } from './trackball' -import { Sketcher } from './Sketcher' +import { Sketcher } from './sketcher/Sketcher' import GUI from '../node_modules/dat.gui/src/dat/gui/GUI.js' import Stats from '../node_modules/three/examples/jsm/libs/stats.module.js'; diff --git a/src/scratch.js b/src/scratch.js deleted file mode 100644 index 8b13789..0000000 --- a/src/scratch.js +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/sketchTools.js b/src/sketchTools.js deleted file mode 100644 index 7d1a1e4..0000000 --- a/src/sketchTools.js +++ /dev/null @@ -1,56 +0,0 @@ -import * as THREE from '../node_modules/three/src/Three' - - -export function sketchLine(mouseLoc) { - this.p1Geom = new THREE.BufferGeometry().setAttribute('position', - new THREE.BufferAttribute(new Float32Array(3), 3) - ) - this.p1 = new THREE.Points(this.p1Geom, - new THREE.PointsMaterial().copy(this.pointMaterial) - ); - this.p1.matrixAutoUpdate = false; - this.p1.constraints = new Set() - - this.p2Geom = new THREE.BufferGeometry().setAttribute('position', - new THREE.BufferAttribute(new Float32Array(3), 3) - ) - this.p2 = new THREE.Points( - this.p2Geom, - new THREE.PointsMaterial().copy(this.pointMaterial) - ); - this.p2.matrixAutoUpdate = false; - this.p2.constraints = new Set() - - this.lineGeom = new THREE.BufferGeometry().setAttribute('position', - new THREE.BufferAttribute(new Float32Array(6), 3) - ); - this.line = new THREE.Line(this.lineGeom, - new THREE.LineBasicMaterial().copy(this.lineMaterial) - ); - this.line.matrixAutoUpdate = false; - this.line.frustumCulled = false; - this.line.constraints = new Set() - - - - this.lineGeom.attributes.position.set(mouseLoc) - this.p1Geom.attributes.position.set(mouseLoc) - - this.toPush = [this.p1, this.p2, this.line]; - - if (this.subsequent) { - - this.constraints.set(this.c_id, - [ - 'coincident', -1, - [this.children[this.children.length - 2], this.p1, -1, -1] - ] - ) - - this.p1.constraints.add(this.c_id) - this.children[this.children.length - 2].constraints.add(this.c_id) - this.c_id += 1 - - } - -} \ No newline at end of file diff --git a/src/sketcher/Sketcher.js b/src/sketcher/Sketcher.js new file mode 100644 index 0000000..20a5329 --- /dev/null +++ b/src/sketcher/Sketcher.js @@ -0,0 +1,281 @@ + +import { Matrix4 } from 'three'; + +import * as THREE from 'three/src/Three' + +import { onClick_1, onClick_2, beforeClick_2, clear } from './drawEvents' +import { onHover, onDrag, onPick, onRelease } from './pickEvents' + + +const lineMaterial = new THREE.LineBasicMaterial({ + linewidth: 2, + color: 0x555555, +}) + +const pointMaterial = new THREE.PointsMaterial({ + color: 0x555555, + size: 4, +}) + +class Sketcher extends THREE.Group { + constructor(camera, domElement, plane) { + super() + this.camera = camera; + this.domElement = domElement; + this.matrixAutoUpdate = false; + + this.plane = plane; + this.sketchNormal = new THREE.Vector3(0, 0, 1) + this.orientSketcher(plane) + this.add(new THREE.PlaneHelper(this.plane, 1, 0xffff00)); + + this.raycaster = new THREE.Raycaster(); + this.raycaster.params.Line.threshold = 0.4; + this.raycaster.params.Points.threshold = 0.4; + + // [0]:x, [1]:y, [2]:z + this.objIdx = new Map() + this.max_pts = 1000 + this.ptsBuf = new Float32Array(this.max_pts * 3).fill(NaN) + + // [0]:type, [1]:pt1, [2]:pt2, [3]:pt3, [4]:pt4 + this.linkedObjs = new Map() + this.l_id = 0; + this.max_links = 1000 + this.linksBuf = new Float32Array(this.max_links * 5).fill(NaN) + this.linkNum = { + 'line': 0, + 'arc': 1 + } + + // [0]:type, [1]:val, [2]:pt1, [3]:pt2, [4]:lk1, [5]:lk2 + this.constraints = new Map() + this.c_id = 0; + this.max_constraints = 1000 + this.constraintsBuf = new Float32Array(this.max_constraints * 6).fill(NaN) + this.contraintNum = { + 'coincident': 0, + 'parallel': 1 + } + + + this.onClick_1 = onClick_1.bind(this); + this.beforeClick_2 = beforeClick_2.bind(this); + this.onClick_2 = onClick_2.bind(this); + + this.onHover = onHover.bind(this); + this.onPick = onPick.bind(this); + this.onDrag = onDrag.bind(this); + this.onRelease = onRelease.bind(this); + + this.onKeyPress = this.onKeyPress.bind(this); + + window.addEventListener('keydown', this.onKeyPress) + domElement.addEventListener('pointerdown', this.onPick) + domElement.addEventListener('pointermove', this.onHover) + + this.mode = "" + this.subsequent = false; + this.selected = new Set() + this.target = new THREE.Vector3(); + } + + orientSketcher() { + const theta = this.sketchNormal.angleTo(this.plane.normal) + const axis = this.sketchNormal.clone().cross(this.plane.normal).normalize() + const rot = new THREE.Matrix4().makeRotationAxis(axis, theta) + const trans = new THREE.Matrix4().makeTranslation(0, 0, this.plane.constant) + + this.matrix = rot.multiply(trans) // world matrix will auto update in next render + this.inverse = this.matrix.clone().invert() + + } + + onKeyPress(e) { + switch (e.key) { + case 'Escape': + clear.bind(this)() + this.mode = "" + break; + case 'l': + if (this.mode == 'line') { + this.clear() + } + this.domElement.addEventListener('pointerdown', this.onClick_1) + this.mode = "line" + break; + case 'a': + this.domElement.addEventListener('pointerdown', this.onClick_1) + this.mode = "arc" + break; + case 'd': + this.deleteSelected() + break; + case '=': + this.plane.applyMatrix4(new Matrix4().makeRotationY(0.1)) + this.orientSketcher() + this.dispatchEvent({ type: 'change' }) + break; + case '-': + this.plane.applyMatrix4(new Matrix4().makeRotationY(-0.1)) + this.orientSketcher() + this.dispatchEvent({ type: 'change' }) + break; + } + } + + + + deleteSelected() { + let minI = this.children.length; + + for (let obj of this.selected) { + minI = Math.min(minI, this.delete(obj)) + } + + this.updatePointsBuffer(minI) + this.updateOtherBuffers() + + this.selected.clear() + this.dispatchEvent({ type: 'change' }) + } + + deleteConstraints(c_id) { + for (let ob of this.constraints.get(c_id)[2]) { + if (ob == -1) continue + ob.constraints.delete(c_id) + } + this.constraints.delete(c_id) + } + + updateOtherBuffers() { + let i = 0 + for (let [key, obj] of this.constraints) { + this.constraintsBuf.set( + [ + this.contraintNum[obj[0]], obj[1], + ...obj[2].map(ele => this.objIdx.get(ele.id) ?? -1), + ], + (i) * 6 + ) + i++ + } + + i = 0; + for (let [key, obj] of this.linkedObjs) { + this.linksBuf.set( + [ + this.linkNum[obj[0]], + ...obj[1].map(ele => this.objIdx.get(ele.id) ?? -1), + ], + (i) * 5 + ) + i++ + } + + } + + delete(obj) { + let link = this.linkedObjs.get(obj.l_id) + if (!link) return Infinity; + link = link[1] + + let i = this.children.indexOf(link[0]) + + for (let j = 0; j < link.length; j++) { + const obj = this.children[i + j] + obj.geometry.dispose() + obj.material.dispose() + + for (let c_id of obj.constraints) { + this.deleteConstraints(c_id) + } + } + + this.children.splice(i, link.length) + + this.linkedObjs.delete(obj.l_id) + + return i + } + + updatePointsBuffer(startingIdx = 0) { + for (let i = startingIdx; i < this.children.length; i++) { + const obj = this.children[i] + this.objIdx.set(obj.id, i) + if (obj.type == "Points") { + this.ptsBuf.set(obj.geometry.attributes.position.array, 3 * i) + } + } + } + + + getLocation(e) { + this.raycaster.setFromCamera( + new THREE.Vector2( + (e.clientX / window.innerWidth) * 2 - 1, + - (e.clientY / window.innerHeight) * 2 + 1 + ), + this.camera + ); + + this.raycaster.ray.intersectPlane(this.plane, this.target).applyMatrix4(this.inverse) + + return this.target.toArray() + } + + + + solve() { + + const pts_buffer = + Module._malloc(this.ptsBuf.length * this.ptsBuf.BYTES_PER_ELEMENT) + Module.HEAPF32.set(this.ptsBuf, pts_buffer >> 2) + + const constraints_buffer = + Module._malloc(this.constraintsBuf.length * this.constraintsBuf.BYTES_PER_ELEMENT) + Module.HEAPF32.set(this.constraintsBuf, constraints_buffer >> 2) + + const links_buffer = + Module._malloc(this.linksBuf.length * this.linksBuf.BYTES_PER_ELEMENT) + Module.HEAPF32.set(this.linksBuf, links_buffer >> 2) + + Module["_solver"]( + this.children.length, pts_buffer, + this.constraints.size, constraints_buffer, + this.linkedObjs.size, links_buffer) + + let ptr = pts_buffer >> 2; + + + for (let i = 0; i < this.children.length; i += 1) { + + const pos = this.children[i].geometry.attributes.position; + if (isNaN(Module.HEAPF32[ptr])) { + + pos.array[0] = Module.HEAPF32[ptr - 6] + pos.array[1] = Module.HEAPF32[ptr - 5] + pos.array[3] = Module.HEAPF32[ptr - 3] + pos.array[4] = Module.HEAPF32[ptr - 2] + } else { + pos.array[0] = Module.HEAPF32[ptr] + pos.array[1] = Module.HEAPF32[ptr + 1] + } + ptr += 3; + pos.needsUpdate = true; + } + + + this.dispatchEvent({ type: 'change' }) + + Module._free(pts_buffer) + Module._free(links_buffer) + Module._free(constraints_buffer) + } + +} + + + + +export { Sketcher, lineMaterial, pointMaterial } \ No newline at end of file diff --git a/src/sketcher/drawEvents.js b/src/sketcher/drawEvents.js new file mode 100644 index 0000000..3c8895d --- /dev/null +++ b/src/sketcher/drawEvents.js @@ -0,0 +1,73 @@ + +import { sketchArc, sketchArc2 } from './sketchArc' +import { sketchLine, sketchLine2 } from './sketchLine' + +export function onClick_1(e) { + if (e.buttons !== 1) return + this.domElement.removeEventListener('pointerdown', this.onClick_1) + const mouseLoc = this.getLocation(e); + + if (this.mode == "line") { + this.toPush = sketchLine.call(this, mouseLoc) + } else if (this.mode == "arc") { + this.toPush = sketchArc(mouseLoc) + } + + this.updatePoint = this.children.length + this.add(...this.toPush) + + this.linkedObjs.set(this.l_id, [this.mode, this.toPush]) + for (let obj of this.toPush) { + obj.l_id = this.l_id + } + this.l_id += 1 + + this.domElement.addEventListener('pointermove', this.beforeClick_2) + this.domElement.addEventListener('pointerdown', this.onClick_2) +} + + +export function beforeClick_2(e) { + const mouseLoc = this.getLocation(e); + + if (this.mode == "line") { + sketchLine2(mouseLoc, this.toPush) + } else if (this.mode == 'arc') { + sketchArc2(mouseLoc, this.toPush) + } + + this.dispatchEvent({ type: 'change' }) +} + +export function onClick_2(e) { + if (e.buttons !== 1) return; + this.domElement.removeEventListener('pointermove', this.beforeClick_2); + this.domElement.removeEventListener('pointerdown', this.onClick_2); + + this.updatePointsBuffer(this.updatePoint) + this.updateOtherBuffers() + + if (this.mode == "line") { + + this.subsequent = true + this.onClick_1(e) + + } else if (this.mode == "arc") { + // this.domElement.addEventListener('pointermove', this.beforeClick_3) + } +} + +export function clear() { + 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.delete(this.children[this.children.length - 1]) + + this.dispatchEvent({ type: 'change' }) + this.subsequent = false + } +} diff --git a/src/sketcher/geometry.js b/src/sketcher/geometry.js new file mode 100644 index 0000000..7737527 --- /dev/null +++ b/src/sketcher/geometry.js @@ -0,0 +1,50 @@ +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 new file mode 100644 index 0000000..3feb7dc --- /dev/null +++ b/src/sketcher/pickEvents.js @@ -0,0 +1,84 @@ +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) + } + + this.raycaster.setFromCamera( + new THREE.Vector2( + (e.clientX / window.innerWidth) * 2 - 1, + - (e.clientY / window.innerHeight) * 2 + 1 + ), + this.camera + ); + + const hoverPts = this.raycaster.intersectObjects(this.children) + // console.log(hoverPts) + + if (hoverPts.length) { + let minDist = Infinity; + let idx = 0 + for (let i = 0; i < hoverPts.length; i++) { + if (hoverPts[i].distanceToRay && hoverPts[i].distanceToRay <= minDist) { + minDist = hoverPts[i].distanceToRay + idx = i + } + } + + hoverPts[idx].object.material.color.set(0xff0000) + this.hovered = hoverPts[idx].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 + ) + this.domElement.addEventListener('pointermove', this.onDrag); + this.domElement.addEventListener('pointerup', this.onRelease) + } + } else { + for (let obj of this.selected) { + obj.material.color.set(0x555555) + } + this.dispatchEvent({ type: 'change' }) + this.selected.clear() + } +} + +export function onDrag(e) { + const mouseLoc = this.getLocation(e); + + this.ptsBuf.set( + mouseLoc, + this.objIdx.get(this.children[this.grabPtIdx].id) * 3 + ) + + this.solve() + this.dispatchEvent({ type: 'change' }) +} + + +export function onRelease() { + this.domElement.removeEventListener('pointermove', this.onDrag) + this.domElement.removeEventListener('pointerup', this.onRelease) + this.children[this.grabPtIdx].geometry.computeBoundingSphere() +} + diff --git a/src/sketcher/sketchArc.js b/src/sketcher/sketchArc.js new file mode 100644 index 0000000..07d049a --- /dev/null +++ b/src/sketcher/sketchArc.js @@ -0,0 +1,63 @@ +import * as THREE from 'three/src/Three' + +import { get2PtArc, get3PtArc } from './geometry' +import {lineMaterial, pointMaterial} from './Sketcher' + +export function sketchArc(mouseLoc) { + const p1Geom = new THREE.BufferGeometry().setAttribute('position', + new THREE.BufferAttribute(new Float32Array(mouseLoc), 3) + ) + const p1 = new THREE.Points(p1Geom, + new THREE.PointsMaterial().copy(pointMaterial) + ); + p1.matrixAutoUpdate = false; + p1.constraints = new Set() + + const p2Geom = new THREE.BufferGeometry().setAttribute('position', + new THREE.BufferAttribute(new Float32Array(3), 3) + ) + const p2 = new THREE.Points( + p2Geom, + new THREE.PointsMaterial().copy(pointMaterial) + ); + p2.matrixAutoUpdate = false; + p2.constraints = new Set() + + const arcGeom = new THREE.BufferGeometry().setAttribute('position', + new THREE.BufferAttribute(new Float32Array(3 * 37), 3) + ) + + const arc = new THREE.Line(arcGeom, + new THREE.LineBasicMaterial().copy(lineMaterial) + ); + arc.frustumCulled = false; + + const p3Geom = new THREE.BufferGeometry().setAttribute('position', + new THREE.BufferAttribute(new Float32Array(3), 3) + ) + const p3 = new THREE.Points(p3Geom, + new THREE.PointsMaterial().copy(pointMaterial) + ); + + return [p1, p2, p3, arc] +} + +export function sketchArc2(mouseLoc, toPush) { + const [p1, p2, p3, arc] = toPush + + p2.geometry.attributes.position.set(mouseLoc); + p2.geometry.attributes.position.needsUpdate = true; + p2.geometry.computeBoundingSphere(); + + const [points, center] = get2PtArc( + p1.geometry.attributes.position.array, + p2.geometry.attributes.position.array + ) + arc.geometry.attributes.position.set( + points + ); + arc.geometry.attributes.position.needsUpdate = true; + p3.geometry.attributes.position.set(center); + p3.geometry.attributes.position.needsUpdate = true; + p3.geometry.computeBoundingSphere() +} \ No newline at end of file diff --git a/src/sketcher/sketchLine.js b/src/sketcher/sketchLine.js new file mode 100644 index 0000000..1371911 --- /dev/null +++ b/src/sketcher/sketchLine.js @@ -0,0 +1,70 @@ +import * as THREE from 'three/src/Three' + +import {lineMaterial, pointMaterial} from './Sketcher' + +export function sketchLine(mouseLoc) { + + const p1Geom = new THREE.BufferGeometry().setAttribute('position', + new THREE.BufferAttribute(new Float32Array(3), 3) + ) + const p1 = new THREE.Points(p1Geom, + new THREE.PointsMaterial().copy(pointMaterial) + ); + p1.matrixAutoUpdate = false; + p1.constraints = new Set() + + const p2Geom = new THREE.BufferGeometry().setAttribute('position', + new THREE.BufferAttribute(new Float32Array(3), 3) + ) + const p2 = new THREE.Points( + p2Geom, + new THREE.PointsMaterial().copy(pointMaterial) + ); + p2.matrixAutoUpdate = false; + p2.constraints = new Set() + + const lineGeom = new THREE.BufferGeometry().setAttribute('position', + new THREE.BufferAttribute(new Float32Array(6), 3) + ); + const line = new THREE.Line(lineGeom, + new THREE.LineBasicMaterial().copy(lineMaterial) + ); + line.matrixAutoUpdate = false; + line.frustumCulled = false; + line.constraints = new Set() + + + lineGeom.attributes.position.set(mouseLoc) + p1Geom.attributes.position.set(mouseLoc) + + if (this.subsequent) { + + this.constraints.set(this.c_id, + [ + 'coincident', -1, + [this.children[this.children.length - 2], p1, -1, -1] + ] + ) + + p1.constraints.add(this.c_id) + this.children[this.children.length - 2].constraints.add(this.c_id) + this.c_id += 1 + + } + + + + return [p1, p2, line]; +} + +export function sketchLine2(mouseLoc, toPush) { + + const [p1, p2, line] = toPush + + p2.geometry.attributes.position.set(mouseLoc); + p2.geometry.attributes.position.needsUpdate = true; + p2.geometry.computeBoundingSphere(); + + line.geometry.attributes.position.set(mouseLoc, 3) + line.geometry.attributes.position.needsUpdate = true; +} \ No newline at end of file diff --git a/src/utils.js b/src/utils.js index b84b47b..e69de29 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,10 +0,0 @@ -export function KeyboardController() { - this.state=""; - window.addEventListener('keydown', (e)=> { - if (e.key == "Escape"){ - this.state = "" - } else { - this.state = e.key - } - }) -} \ No newline at end of file diff --git a/wasm/CDemo.c b/wasm/CDemo.c new file mode 100644 index 0000000..cf6107e --- /dev/null +++ b/wasm/CDemo.c @@ -0,0 +1,269 @@ +/*----------------------------------------------------------------------------- + * Some sample code for slvs.dll. We draw some geometric entities, provide + * initial guesses for their positions, and then constrain them. The solver + * calculates their new positions, in order to satisfy the constraints. + * + * Copyright 2008-2013 Jonathan Westhues. + *---------------------------------------------------------------------------*/ +#ifdef WIN32 +# include +#endif +#include +#include +#include +#include + +#include + +static Slvs_System sys; + +static void *CheckMalloc(size_t n) +{ + void *r = malloc(n); + if(!r) { + printf("out of memory!\n"); + exit(-1); + } + return r; +} + +/*----------------------------------------------------------------------------- + * An example of a constraint in 3d. We create a single group, with some + * entities and constraints. + *---------------------------------------------------------------------------*/ +void Example3d() +{ + /* This will contain a single group, which will arbitrarily number 1. */ + Slvs_hGroup g = 1; + + /* A point, initially at (x y z) = (10 10 10) */ + sys.param[sys.params++] = Slvs_MakeParam(1, g, 10.0); + sys.param[sys.params++] = Slvs_MakeParam(2, g, 10.0); + sys.param[sys.params++] = Slvs_MakeParam(3, g, 10.0); + sys.entity[sys.entities++] = Slvs_MakePoint3d(101, g, 1, 2, 3); + /* and a second point at (20 20 20) */ + sys.param[sys.params++] = Slvs_MakeParam(4, g, 20.0); + sys.param[sys.params++] = Slvs_MakeParam(5, g, 20.0); + sys.param[sys.params++] = Slvs_MakeParam(6, g, 20.0); + sys.entity[sys.entities++] = Slvs_MakePoint3d(102, g, 4, 5, 6); + /* and a line segment connecting them. */ + sys.entity[sys.entities++] = Slvs_MakeLineSegment(200, g, + SLVS_FREE_IN_3D, 101, 102); + + /* The distance between the points should be 30.0 units. */ + sys.constraint[sys.constraints++] = Slvs_MakeConstraint( + 1, g, + SLVS_C_PT_PT_DISTANCE, + SLVS_FREE_IN_3D, + 30.0, + 101, 102, 0, 0); + + /* Let's tell the solver to keep the second point as close to constant + * as possible, instead moving the first point. */ + sys.dragged[0] = 4; + sys.dragged[1] = 5; + sys.dragged[2] = 6; + + /* Now that we have written our system, we solve. */ + Slvs_Solve(&sys, g); + + if(sys.result == SLVS_RESULT_OKAY) { + printf("okay; now at (%.3f %.3f %.3f)\n" + " (%.3f %.3f %.3f)\n", + sys.param[0].val, sys.param[1].val, sys.param[2].val, + sys.param[3].val, sys.param[4].val, sys.param[5].val); + printf("%d DOF\n", sys.dof); + } else { + printf("solve failed"); + } +} + +/*----------------------------------------------------------------------------- + * An example of a constraint in 2d. In our first group, we create a workplane + * along the reference frame's xy plane. In a second group, we create some + * entities in that group and dimension them. + *---------------------------------------------------------------------------*/ +void Example2d() +{ + Slvs_hGroup g; + double qw, qx, qy, qz; + + g = 1; + /* First, we create our workplane. Its origin corresponds to the origin + * of our base frame (x y z) = (0 0 0) */ + sys.param[sys.params++] = Slvs_MakeParam(1, g, 0.0); + sys.param[sys.params++] = Slvs_MakeParam(2, g, 0.0); + sys.param[sys.params++] = Slvs_MakeParam(3, g, 0.0); + sys.entity[sys.entities++] = Slvs_MakePoint3d(101, g, 1, 2, 3); + /* and it is parallel to the xy plane, so it has basis vectors (1 0 0) + * and (0 1 0). */ + Slvs_MakeQuaternion(1, 0, 0, + 0, 1, 0, &qw, &qx, &qy, &qz); + sys.param[sys.params++] = Slvs_MakeParam(4, g, qw); + sys.param[sys.params++] = Slvs_MakeParam(5, g, qx); + sys.param[sys.params++] = Slvs_MakeParam(6, g, qy); + sys.param[sys.params++] = Slvs_MakeParam(7, g, qz); + sys.entity[sys.entities++] = Slvs_MakeNormal3d(102, g, 4, 5, 6, 7); + + sys.entity[sys.entities++] = Slvs_MakeWorkplane(200, g, 101, 102); + + /* Now create a second group. We'll solve group 2, while leaving group 1 + * constant; so the workplane that we've created will be locked down, + * and the solver can't move it. */ + g = 2; + /* These points are represented by their coordinates (u v) within the + * workplane, so they need only two parameters each. */ + sys.param[sys.params++] = Slvs_MakeParam(11, g, 10.0); + sys.param[sys.params++] = Slvs_MakeParam(12, g, 20.0); + sys.entity[sys.entities++] = Slvs_MakePoint2d(301, g, 200, 11, 12); + + sys.param[sys.params++] = Slvs_MakeParam(13, g, 20.0); + sys.param[sys.params++] = Slvs_MakeParam(14, g, 10.0); + sys.entity[sys.entities++] = Slvs_MakePoint2d(302, g, 200, 13, 14); + + /* And we create a line segment with those endpoints. */ + sys.entity[sys.entities++] = Slvs_MakeLineSegment(400, g, + 200, 301, 302); + + /* Now three more points. */ + sys.param[sys.params++] = Slvs_MakeParam(15, g, 100.0); + sys.param[sys.params++] = Slvs_MakeParam(16, g, 120.0); + sys.entity[sys.entities++] = Slvs_MakePoint2d(303, g, 200, 15, 16); + + sys.param[sys.params++] = Slvs_MakeParam(17, g, 120.0); + sys.param[sys.params++] = Slvs_MakeParam(18, g, 110.0); + sys.entity[sys.entities++] = Slvs_MakePoint2d(304, g, 200, 17, 18); + + sys.param[sys.params++] = Slvs_MakeParam(19, g, 115.0); + sys.param[sys.params++] = Slvs_MakeParam(20, g, 115.0); + sys.entity[sys.entities++] = Slvs_MakePoint2d(305, g, 200, 19, 20); + + /* And arc, centered at point 303, starting at point 304, ending at + * point 305. */ + sys.entity[sys.entities++] = Slvs_MakeArcOfCircle(401, g, 200, 102, + 303, 304, 305); + + /* Now one more point, and a distance */ + sys.param[sys.params++] = Slvs_MakeParam(21, g, 200.0); + sys.param[sys.params++] = Slvs_MakeParam(22, g, 200.0); + sys.entity[sys.entities++] = Slvs_MakePoint2d(306, g, 200, 21, 22); + + sys.param[sys.params++] = Slvs_MakeParam(23, g, 30.0); + sys.entity[sys.entities++] = Slvs_MakeDistance(307, g, 200, 23); + + /* And a complete circle, centered at point 306 with radius equal to + * distance 307. The normal is 102, the same as our workplane. */ + sys.entity[sys.entities++] = Slvs_MakeCircle(402, g, 200, + 306, 102, 307); + + + /* The length of our line segment is 30.0 units. */ + sys.constraint[sys.constraints++] = Slvs_MakeConstraint( + 1, g, + SLVS_C_PT_PT_DISTANCE, + 200, + 30.0, + 301, 302, 0, 0); + + /* And the distance from our line segment to the origin is 10.0 units. */ + sys.constraint[sys.constraints++] = Slvs_MakeConstraint( + 2, g, + SLVS_C_PT_LINE_DISTANCE, + 200, + 10.0, + 101, 0, 400, 0); + /* And the line segment is vertical. */ + sys.constraint[sys.constraints++] = Slvs_MakeConstraint( + 3, g, + SLVS_C_VERTICAL, + 200, + 0.0, + 0, 0, 400, 0); + /* And the distance from one endpoint to the origin is 15.0 units. */ + sys.constraint[sys.constraints++] = Slvs_MakeConstraint( + 4, g, + SLVS_C_PT_PT_DISTANCE, + 200, + 15.0, + 301, 101, 0, 0); +#if 0 + /* And same for the other endpoint; so if you add this constraint then + * the sketch is overconstrained and will signal an error. */ + sys.constraint[sys.constraints++] = Slvs_MakeConstraint( + 5, g, + SLVS_C_PT_PT_DISTANCE, + 200, + 18.0, + 302, 101, 0, 0); +#endif /* 0 */ + + /* The arc and the circle have equal radius. */ + sys.constraint[sys.constraints++] = Slvs_MakeConstraint( + 6, g, + SLVS_C_EQUAL_RADIUS, + 200, + 0.0, + 0, 0, 401, 402); + /* The arc has radius 17.0 units. */ + sys.constraint[sys.constraints++] = Slvs_MakeConstraint( + 7, g, + SLVS_C_DIAMETER, + 200, + 17.0*2, + 0, 0, 401, 0); + + /* If the solver fails, then ask it to report which constraints caused + * the problem. */ + sys.calculateFaileds = 1; + + /* And solve. */ + Slvs_Solve(&sys, g); + + if(sys.result == SLVS_RESULT_OKAY) { + printf("solved okay\n"); + printf("line from (%.3f %.3f) to (%.3f %.3f)\n", + sys.param[7].val, sys.param[8].val, + sys.param[9].val, sys.param[10].val); + + printf("arc center (%.3f %.3f) start (%.3f %.3f) finish (%.3f %.3f)\n", + sys.param[11].val, sys.param[12].val, + sys.param[13].val, sys.param[14].val, + sys.param[15].val, sys.param[16].val); + + printf("circle center (%.3f %.3f) radius %.3f\n", + sys.param[17].val, sys.param[18].val, + sys.param[19].val); + printf("%d DOF\n", sys.dof); + } else { + int i; + printf("solve failed: problematic constraints are:"); + for(i = 0; i < sys.faileds; i++) { + printf(" %d", sys.failed[i]); + } + printf("\n"); + if(sys.result == SLVS_RESULT_INCONSISTENT) { + printf("system inconsistent\n"); + } else { + printf("system nonconvergent\n"); + } + } +} + +int main() +{ + sys.param = CheckMalloc(50*sizeof(sys.param[0])); + sys.entity = CheckMalloc(50*sizeof(sys.entity[0])); + sys.constraint = CheckMalloc(50*sizeof(sys.constraint[0])); + + sys.failed = CheckMalloc(50*sizeof(sys.failed[0])); + sys.faileds = 50; + + /*Example3d();*/ + for(;;) { + Example2d(); + sys.params = sys.constraints = sys.entities = 0; + break; + } + return 0; +} + diff --git a/wasm/DOC.txt b/wasm/DOC.txt new file mode 100644 index 0000000..f64f3ac --- /dev/null +++ b/wasm/DOC.txt @@ -0,0 +1,467 @@ + +INTRODUCTION +============ + +A sketch in SolveSpace consists of three basic elements: parameters, +entities, and constraints. + +A parameter (Slvs_Param) is a single real number, represented internally +by a double-precision floating point variable. The parameters are unknown +variables that the solver modifies in order to satisfy the constraints. + +An entity (Slvs_Entity) is a geometric thing, like a point or a line +segment or a circle. Entities are defined in terms of parameters, +and in terms of other entities. For example, a point in three-space +is represented by three parameters, corresponding to its x, y, and z +coordinates in our base coordinate frame. A line segment is represented +by two point entities, corresponding to its endpoints. + +A constraint (Slvs_Constraint) is a geometric property of an entity, +or a relationship among multiple entities. For example, a point-point +distance constraint will set the distance between two point entities. + +Parameters, entities, and constraints are typically referenced by their +handles (Slvs_hParam, Slvs_hEntity, Slvs_hConstraint). These handles are +32-bit integer values starting from 1. The zero handle is reserved. Each +object has a unique handle within its type (but it's acceptable, for +example to have a constraint with an Slvs_hConstraint of 7, and also to +have an entity with an Slvs_hEntity of 7). The use of handles instead +of pointers helps to avoid memory corruption. + +Entities and constraints are assigned into groups. A group is a set of +entities and constraints that is solved simultaneously. In a parametric +CAD system, a single group would typically correspond to a single sketch. +Constraints within a group may refer to entities outside that group, +but only the entities within that group will be modified by the solver. + +Consider point A in group 1, and point B in group 2. We have a constraint +in group 2 that makes the points coincident. When we solve group 2, the +solver is allowed to move point B to place it on top of point A. It is +not allowed to move point A to put it on top of point B, because point +A is outside the group being solved. + +This corresponds to the typical structure of a parametric CAD system. In a +later sketch, we may constrain our entities against existing geometry from +earlier sketches. The constraints will move the entities in our current +sketch, but will not change the geometry from the earlier sketches. + +To use the solver, we first define a set of parameters, entities, and +constraints. We provide an initial guess for each parameter; this is +necessary to achieve convergence, and also determines which solution +gets chosen when (finitely many) multiple solutions exist. Typically, +these initial guesses are provided by the initial configuration in which +the user drew the entities before constraining them. + +We then run the solver for a given group. The entities within that group +are modified in an attempt to satisfy the constraints. + +After running the solver, there are three possible outcomes: + + * All constraints were satisfied to within our numerical + tolerance (i.e., success). The result is equal to SLVS_RESULT_OKAY, + and the parameters in param[] have been updated. + + * The solver can prove that two constraints are inconsistent (for + example, if a line with nonzero length is constrained both + horizontal and vertical). In that case, a list of inconsistent + constraints is generated in failed[]. + + * The solver cannot prove that two constraints are inconsistent, but + it cannot find a solution. In that case, the list of unsatisfied + constraints is generated in failed[]. + + +TYPES OF ENTITIES +================= + +SLVS_E_POINT_IN_3D + + A point in 3d. Defined by three parameters: + + param[0] the point's x coordinate + param[1] y + param[1] z + + +SLVS_E_POINT_IN_2D + + A point within a workplane. Defined by the workplane + + wrkpl + + and by two parameters + + param[0] the point's u coordinate + param[1] v + + within the coordinate system of the workplane. For example, if the + workplane is the zx plane, then u = z and v = x. If the workplane is + parallel to the zx plane, but translated so that the workplane's + origin is (3, 4, 5), then u = z - 5 and v = x - 3. + + +SLVS_E_NORMAL_IN_3D + + A normal. In SolveSpace, "normals" represent a 3x3 rotation matrix + from our base coordinate system to a new frame. Defined by the + unit quaternion + + param[0] w + param[1] x + param[2] y + param[3] z + + where the quaternion is given by w + x*i + y*j + z*k. + + It is useful to think of this quaternion as representing a plane + through the origin. This plane has three associated vectors: basis + vectors U, V that lie within the plane, and normal N that is + perpendicular to it. This means that + + [ U V N ]' + + defines a 3x3 rotation matrix. So U, V, and N all have unit length, + and are orthogonal so that + + U cross V = N + V cross N = U + N cross U = V + + Convenience functions (Slvs_Quaternion*) are provided to convert + between this representation as vectors U, V, N and the unit + quaternion. + + A unit quaternion has only 3 degrees of freedom, but is specified in + terms of 4 parameters. An extra constraint is therefore generated + implicitly, that + + w^2 + x^2 + y^2 + z^2 = 1 + + +SLVS_E_NORMAL_IN_2D + + A normal within a workplane. This is identical to the workplane's + normal, so it is simply defined by + + wrkpl + + This entity type is used, for example, to define a circle that lies + within a workplane. The circle's normal is the same as the workplane's + normal, so we can use an SLVS_E_NORMAL_IN_2D to copy the workplane's + normal. + + +SLVS_E_DISTANCE + + A distance. This entity is used to define the radius of a circle, by + a single parameter + + param[0] r + + +SLVS_E_WORKPLANE + + An oriented plane, somewhere in 3d. This entity therefore has 6 + degrees of freedom: three translational, and three rotational. It is + specified in terms of its origin + + point[0] origin + + and a normal + + normal + + The normal describes three vectors U, V, N, as discussed in the + documentation for SLVS_E_NORMAL_IN_3D. The plane is therefore given + by the equation + + p = origin + s*U + t*V + + for any scalar s and t. + + +SLVS_E_LINE_SEGMENT + + A line segment between two endpoints + + point[0] + point[1] + + +SLVS_E_CUBIC + + A nonrational cubic Bezier segment + + point[0] starting point P0 + point[1] control point P1 + point[2] control point P2 + point[3] ending point P3 + + The curve then has equation + + p(t) = P0*(1 - t)^3 + 3*P1*(1 - t)^2*t + 3*P2*(1 - t)*t^2 + P3*t^3 + + as t goes from 0 to 1. + + +SLVS_E_CIRCLE + + A complete circle. The circle lies within a plane with normal + + normal + + The circle is centered at + + point[0] + + The circle's radius is + + distance + + +SLVS_E_ARC_OF_CIRCLE + + An arc of a circle. An arc must always lie within a workplane; it + cannot be free in 3d. So it is specified with a workplane + + wrkpl + + It is then defined by three points + + point[0] center of the circle + point[1] beginning of the arc + point[2] end of the arc + + and its normal + + normal identical to the normal of the workplane + + The arc runs counter-clockwise from its beginning to its end (with + the workplane's normal pointing towards the viewer). If the beginning + and end of the arc are coincident, then the arc is considered to + represent a full circle. + + This representation has an extra degree of freedom. An extra + constraint is therefore generated implicitly, so that + + distance(center, beginning) = distance(center, end) + + +TYPES OF CONSTRAINTS +==================== + +Many constraints can apply either in 3d, or in a workplane. This is +determined by the wrkpl member of the constraint. If that member is set +to SLVS_FREE_IN_3D, then the constraint applies in 3d. If that member +is set equal to a workplane, the constraint applies projected into that +workplane. (For example, a constraint on the distance between two points +actually applies to the projected distance). + +Constraints that may be used in 3d or projected into a workplane are +marked with a single star (*). Constraints that must always be used with +a workplane are marked with a double star (**). Constraints that ignore +the wrkpl member are marked with no star. + +SLVS_C_PT_PT_DISTANCE* + + The distance between points ptA and ptB is equal to valA. This is an + unsigned distance, so valA must always be positive. + +SLVS_C_PROJ_PT_DISTANCE + + The distance between points ptA and ptB, as projected along the line + or normal entityA, is equal to valA. This is a signed distance. + +SLVS_C_POINTS_COINCIDENT* + + Points ptA and ptB are coincident (i.e., exactly on top of each + other). + +SLVS_C_PT_PLANE_DISTANCE + + The distance from point ptA to workplane entityA is equal to + valA. This is a signed distance; positive versus negative valA + correspond to a point that is above vs. below the plane. + +SLVS_C_PT_LINE_DISTANCE* + + The distance from point ptA to line segment entityA is equal to valA. + + If the constraint is projected, then valA is a signed distance; + positive versus negative valA correspond to a point that is above + vs. below the line. + + If the constraint applies in 3d, then valA must always be positive. + +SLVS_C_PT_IN_PLANE + + The point ptA lies in plane entityA. + +SLVS_C_PT_ON_LINE* + + The point ptA lies on the line entityA. + + Note that this constraint removes one degree of freedom when projected + in to the plane, but two degrees of freedom in 3d. + +SLVS_C_EQUAL_LENGTH_LINES* + + The lines entityA and entityB have equal length. + +SLVS_C_LENGTH_RATIO* + + The length of line entityA divided by the length of line entityB is + equal to valA. + +SLVS_C_LENGTH_DIFFERENCE* + + The lengths of line entityA and line entityB differ by valA. + +SLVS_C_EQ_LEN_PT_LINE_D* + + The length of the line entityA is equal to the distance from point + ptA to line entityB. + +SLVS_C_EQ_PT_LN_DISTANCES* + + The distance from the line entityA to the point ptA is equal to the + distance from the line entityB to the point ptB. + +SLVS_C_EQUAL_ANGLE* + + The angle between lines entityA and entityB is equal to the angle + between lines entityC and entityD. + + If other is true, then the angles are supplementary (i.e., theta1 = + 180 - theta2) instead of equal. + +SLVS_C_EQUAL_LINE_ARC_LEN* + + The length of the line entityA is equal to the length of the circular + arc entityB. + +SLVS_C_SYMMETRIC* + + The points ptA and ptB are symmetric about the plane entityA. This + means that they are on opposite sides of the plane and at equal + distances from the plane, and that the line connecting ptA and ptB + is normal to the plane. + +SLVS_C_SYMMETRIC_HORIZ +SLVS_C_SYMMETRIC_VERT** + + The points ptA and ptB are symmetric about the horizontal or vertical + axis of the specified workplane. + +SLVS_C_SYMMETRIC_LINE** + + The points ptA and ptB are symmetric about the line entityA. + +SLVS_C_AT_MIDPOINT* + + The point ptA lies at the midpoint of the line entityA. + +SLVS_C_HORIZONTAL +SLVS_C_VERTICAL** + + The line connecting points ptA and ptB is horizontal or vertical. Or, + the line segment entityA is horizontal or vertical. If points are + specified then the line segment should be left zero, and if a line + is specified then the points should be left zero. + +SLVS_C_DIAMETER + + The diameter of circle or arc entityA is equal to valA. + +SLVS_C_PT_ON_CIRCLE + + The point ptA lies on the right cylinder obtained by extruding circle + or arc entityA normal to its plane. + +SLVS_C_SAME_ORIENTATION + + The normals entityA and entityB describe identical rotations. This + constraint therefore restricts three degrees of freedom. + +SLVS_C_ANGLE* + + The angle between lines entityA and entityB is equal to valA, where + valA is specified in degrees. This constraint equation is written + in the form + + (A dot B)/(|A||B|) = cos(valA) + + where A and B are vectors in the directions of lines A and B. This + equation does not specify the angle unambiguously; for example, + note that valA = +/- 90 degrees will produce the same equation. + + If other is true, then the constraint is instead that + + (A dot B)/(|A||B|) = -cos(valA) + +SLVS_C_PERPENDICULAR* + + Identical to SLVS_C_ANGLE with valA = 90 degrees. + +SLVS_C_PARALLEL* + + Lines entityA and entityB are parallel. + + Note that this constraint removes one degree of freedom when projected + in to the plane, but two degrees of freedom in 3d. + +SLVS_C_ARC_LINE_TANGENT** + + The arc entityA is tangent to the line entityB. If other is false, + then the arc is tangent at its beginning (point[1]). If other is true, + then the arc is tangent at its end (point[2]). + +SLVS_C_CUBIC_LINE_TANGENT* + + The cubic entityA is tangent to the line entityB. The variable + other indicates: + + if false: the cubic is tangent at its beginning + if true: the cubic is tangent at its end + + The beginning of the cubic is point[0], and the end is point[3]. + +SLVS_C_CURVE_CURVE_TANGENT** + + The two entities entityA and entityB are tangent. These entities can + each be either an arc or a cubic, in any combination. The flags + other and other2 indicate which endpoint of the curve is tangent, + for entityA and entityB respectively: + + if false: the entity is tangent at its beginning + if true: the entity is tangent at its end + + For cubics, point[0] is the beginning, and point[3] is the end. For + arcs, point[1] is the beginning, and point[2] is the end. + +SLVS_C_EQUAL_RADIUS + + The circles or arcs entityA and entityB have equal radius. + +SLVS_C_WHERE_DRAGGED* + + The point ptA is locked at its initial numerical guess, and cannot + be moved. This constrains two degrees of freedom in a workplane, + and three in free space. It's therefore possible for this constraint + to overconstrain the sketch, for example if it's applied to a point + with one remaining degree of freedom. + + +USING THE SOLVER +================ + +The solver is provided as a DLL, and will be usable with most +Windows-based development tools. Examples are provided: + + in C/C++ - CDemo.c + + in VB.NET - VbDemo.vb + + +Copyright 2009-2013 Jonathan Westhues. + diff --git a/wasm/solver.c b/wasm/solver.c index e36251a..c2b52bb 100644 --- a/wasm/solver.c +++ b/wasm/solver.c @@ -77,42 +77,53 @@ int solver(int nPts, float *p_ptr, int nConst, float *c_ptr, int nLinks, float * { if (isnan((float)*p_ptr)) { - p_ptr+=3; + p_ptr += 3; continue; } sys.param[sys.params++] = Slvs_MakeParam(ph++, g, (float)*p_ptr++); sys.param[sys.params++] = Slvs_MakeParam(ph++, g, (float)*p_ptr++); sys.entity[sys.entities++] = Slvs_MakePoint2d(i, g, 200, ph - 1, ph - 2); - p_ptr+=1; + p_ptr += 1; } for (int i = 0; i < nLinks; i++) { - if (*l_ptr++ == 0) + + switch ((int)*l_ptr++) { + case 0: sys.entity[sys.entities++] = Slvs_MakeLineSegment(lh++, g, - 200, (int)*l_ptr, (int)*(l_ptr+1)); - l_ptr += 4; - } else { - l_ptr += 4; + 200, (int)*l_ptr, (int)*(l_ptr + 1)); + break; + case 1: + /* And arc, centered at point 303, starting at point 304, ending at + * point 305. */ + sys.entity[sys.entities++] = Slvs_MakeArcOfCircle(lh++, g, 200, 102, + (int)*(l_ptr + 2), (int)*(l_ptr), (int)*(l_ptr + 1)); + break; + default: + break; } + + l_ptr += 4; } for (int i = 0; i < nConst; i++) { if ((int)*c_ptr == 0) { - c_ptr+=2; + c_ptr += 2; sys.constraint[sys.constraints++] = Slvs_MakeConstraint( con_id++, g, SLVS_C_POINTS_COINCIDENT, 200, 0.0, - (int)*c_ptr, (int)*(c_ptr+1), 0, 0); + (int)*c_ptr, (int)*(c_ptr + 1), 0, 0); c_ptr += 4; - - } else { + } + else + { c_ptr += 6; } } @@ -128,12 +139,12 @@ int solver(int nPts, float *p_ptr, int nConst, float *c_ptr, int nLinks, float * { if (isnan((float)*buf_pt_start)) { - buf_pt_start+=3; + buf_pt_start += 3; continue; } *buf_pt_start++ = (float)sys.param[p_start++].val; *buf_pt_start++ = (float)sys.param[p_start++].val; - buf_pt_start+=1; + buf_pt_start += 1; } } else