refactor
parent
e51d171709
commit
23da7dcf83
561
src/Sketcher.js
561
src/Sketcher.js
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 }
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
|
@ -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;
|
||||
}
|
10
src/utils.js
10
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
|
||||
}
|
||||
})
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue