master
howard 2021-03-25 01:04:13 -07:00
parent e51d171709
commit 23da7dcf83
14 changed files with 1382 additions and 642 deletions

View File

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

View File

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

View File

@ -1 +0,0 @@

View File

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

281
src/sketcher/Sketcher.js Normal file
View File

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

View File

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

50
src/sketcher/geometry.js Normal file
View File

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

View File

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

63
src/sketcher/sketchArc.js Normal file
View File

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

View File

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

View File

@ -1,10 +0,0 @@
export function KeyboardController() {
this.state="";
window.addEventListener('keydown', (e)=> {
if (e.key == "Escape"){
this.state = ""
} else {
this.state = e.key
}
})
}

269
wasm/CDemo.c Normal file
View File

@ -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 <windows.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <slvs.h>
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;
}

467
wasm/DOC.txt Normal file
View File

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

View File

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