three.cad/src/drawDimension.js

526 lines
13 KiB
JavaScript
Raw Normal View History

2021-04-06 19:46:16 +00:00
import * as THREE from '../node_modules/three/src/Three';
import { color } from './shared'
2021-04-03 04:05:28 +00:00
const lineMaterial = new THREE.LineBasicMaterial({
linewidth: 2,
2021-04-05 03:52:17 +00:00
color: color.dimension,
2021-04-03 04:05:28 +00:00
})
const pointMaterial = new THREE.PointsMaterial({
2021-04-05 03:52:17 +00:00
color: color.dimension,
2021-04-03 04:05:28 +00:00
size: 4,
})
2021-04-13 06:41:05 +00:00
export async function drawDimension(cc) {
2021-04-13 07:02:01 +00:00
2021-04-13 07:28:12 +00:00
let selection, line, dimVal, constraint
2021-04-13 07:02:01 +00:00
if (cc == 'd') { ///////////////////////////////
2021-04-13 07:28:12 +00:00
selection = await this.awaitSelection({ point: 2 }, { point: 1, line: 1 })
if (selection == null) return;
2021-04-13 06:41:05 +00:00
line = new THREE.LineSegments(
new THREE.BufferGeometry().setAttribute('position',
new THREE.Float32BufferAttribute(Array(3 * 8).fill(-0.001), 3)
),
lineMaterial.clone()
);
2021-04-08 23:05:52 +00:00
2021-04-13 07:28:12 +00:00
let ptLineOrder
2021-04-13 06:41:05 +00:00
if (selection.every(e => e.userData.type == 'point')) {
2021-04-13 07:01:28 +00:00
dimVal = 0;
2021-04-13 06:41:05 +00:00
for (let i = 0; i < 3; i++) {
dimVal += (selection[0].geometry.attributes.position.array[i] - selection[1].geometry.attributes.position.array[i]) ** 2
}
dimVal = Math.sqrt(dimVal)
} else {
ptLineOrder = selection[0].userData.type == 'point' ? [0, 1] : [1, 0]
const ptArr = selection[ptLineOrder[0]].geometry.attributes.position.array
const lineArr = selection[ptLineOrder[1]].geometry.attributes.position.array
p1.set(lineArr[0], lineArr[1])
p2.set(lineArr[3], lineArr[4])
p3.set(ptArr[0], ptArr[1])
dir = p2.clone().sub(p1).normalize()
disp = p3.clone().sub(p1)
proj = dir.multiplyScalar(disp.dot(dir))
perpOffset = disp.clone().sub(proj)
dimVal = Math.sqrt(perpOffset.x ** 2 + perpOffset.y ** 2)
2021-04-08 23:05:52 +00:00
}
2021-04-13 07:28:12 +00:00
if (ptLineOrder) {
constraint = [
'pt_line_distance', dimVal,
[selection[ptLineOrder[0]].name, -1, selection[ptLineOrder[1]].name, -1]
]
} else {
constraint = [
'pt_pt_distance', dimVal,
[selection[0].name, selection[1].name, -1, -1]
]
}
2021-04-08 23:05:52 +00:00
} else {
2021-04-13 07:28:12 +00:00
selection = await this.awaitSelection({ line: 2 })
if (selection == null) return;
line = new THREE.LineSegments(
new THREE.BufferGeometry().setAttribute('position',
new THREE.Float32BufferAttribute(Array((divisions + 2) * 2 * 3).fill(-0.001), 3)
),
lineMaterial.clone()
);
2021-04-13 06:41:05 +00:00
dimVal = getAngle(selection)
2021-04-13 07:28:12 +00:00
constraint = [
'angle', dimVal,
[-1, -1, selection[0].name, selection[1].name]
]
}////////////////////////////////
const point = new THREE.Points(
new THREE.BufferGeometry().setAttribute('position',
new THREE.Float32BufferAttribute(3, 3)
),
pointMaterial.clone()
)
line.userData.ids = selection.map(e => e.name)
line.layers.enable(2)
point.layers.enable(2)
2021-04-08 23:05:52 +00:00
2021-04-05 08:05:53 +00:00
this.obj3d.children[1].add(line).add(point)
2021-04-05 03:52:17 +00:00
const onMove = this._onMoveDimension(point, line)
point.label = document.createElement('div');
2021-04-13 06:41:05 +00:00
point.label.textContent = dimVal.toFixed(3);
2021-04-05 08:05:53 +00:00
point.label.contentEditable = true;
2021-04-05 03:52:17 +00:00
this.labelContainer.append(point.label)
2021-04-02 19:33:09 +00:00
2021-04-05 03:52:17 +00:00
let onEnd, onKey;
2021-04-04 02:08:19 +00:00
let add = await new Promise((res) => {
2021-04-13 07:02:01 +00:00
onEnd = () => {
if (cc == 'd') { /////////////////////////
2021-04-13 06:41:05 +00:00
point.userData.offset = hyp2.toArray() // save offset vector from hyp2
} else {
point.userData.offset = vecArr[5].toArray()
2021-04-13 07:02:01 +00:00
} ///////////////////////////////////
2021-04-13 06:41:05 +00:00
res(true)
}
2021-04-04 04:54:27 +00:00
onKey = (e) => e.key == 'Escape' && res(false)
2021-04-04 00:59:14 +00:00
this.canvas.addEventListener('pointermove', onMove)
this.canvas.addEventListener('pointerdown', onEnd)
window.addEventListener('keydown', onKey)
})
2021-04-02 19:33:09 +00:00
2021-04-04 00:59:14 +00:00
this.canvas.removeEventListener('pointermove', onMove)
this.canvas.removeEventListener('pointerdown', onEnd)
2021-04-04 04:54:27 +00:00
window.removeEventListener('keydown', onKey)
2021-04-05 03:52:17 +00:00
point.geometry.computeBoundingSphere()
line.geometry.computeBoundingSphere()
2021-04-08 23:05:52 +00:00
2021-04-04 02:08:19 +00:00
if (add) {
2021-04-13 06:41:05 +00:00
2021-04-13 07:28:12 +00:00
this.constraints.set(++this.c_id, constraint)
2021-04-13 06:41:05 +00:00
2021-04-08 23:05:52 +00:00
selection[0].userData.constraints.push(this.c_id)
selection[1].userData.constraints.push(this.c_id)
2021-04-04 04:54:27 +00:00
2021-04-04 02:08:19 +00:00
this.updateOtherBuffers()
line.name = this.c_id
2021-04-05 03:52:17 +00:00
line.userData.type = 'dimension'
2021-04-04 02:08:19 +00:00
point.name = this.c_id
2021-04-05 03:52:17 +00:00
point.userData.type = 'dimension'
2021-04-06 04:52:19 +00:00
point.label.addEventListener('focus', this.updateDim(this.c_id))
2021-04-05 08:05:53 +00:00
2021-04-04 02:08:19 +00:00
} else {
2021-04-05 08:05:53 +00:00
this.obj3d.children[1].children.splice(this.obj3d.children[1].length - 2, 2).forEach(
2021-04-04 02:08:19 +00:00
e => {
e.geometry.dispose()
e.material.dispose()
}
)
2021-04-05 08:05:53 +00:00
this.labelContainer.removeChild(this.labelContainer.lastChild);
2021-04-04 02:08:19 +00:00
sc.render()
}
2021-04-02 19:33:09 +00:00
2021-04-03 04:05:28 +00:00
return
2021-04-02 19:33:09 +00:00
}
2021-04-09 02:43:14 +00:00
2021-04-06 04:52:19 +00:00
export function updateDim(c_id) {
return (ev_focus) => {
2021-04-09 02:43:14 +00:00
let value = ev_focus.target.textContent
2021-04-06 04:52:19 +00:00
document.addEventListener('keydown', (e) => {
if (e.key == 'Enter') {
e.preventDefault()
const ent = this.constraints.get(c_id)
ent[1] = parseFloat(ev_focus.target.textContent)
2021-04-09 02:43:14 +00:00
value = ent[1]
2021-04-06 04:52:19 +00:00
this.constraints.set(c_id, ent)
this.updateOtherBuffers()
this.solve()
sc.render()
ev_focus.target.blur()
this.updateBoundingSpheres()
} else if (e.key == 'Escape') {
ev_focus.target.textContent = value
getSelection().empty()
ev_focus.target.blur()
}
})
}
2021-04-08 23:05:52 +00:00
}
2021-04-06 04:52:19 +00:00
2021-04-09 02:43:14 +00:00
let ids, _p1, _p2
2021-04-05 03:52:17 +00:00
export function _onMoveDimension(point, line) {
2021-04-04 04:54:27 +00:00
2021-04-06 21:10:07 +00:00
ids = line.userData.ids
2021-04-04 09:36:41 +00:00
2021-04-06 21:10:07 +00:00
_p1 = this.obj3d.children[this.objIdx.get(ids[0])].geometry.attributes.position.array
_p2 = this.obj3d.children[this.objIdx.get(ids[1])].geometry.attributes.position.array
2021-04-05 03:52:17 +00:00
let loc;
2021-04-04 04:54:27 +00:00
return (e) => {
loc = this.getLocation(e)
2021-04-04 09:36:41 +00:00
2021-04-04 04:54:27 +00:00
p3.set(loc.x, loc.y)
2021-04-05 03:52:17 +00:00
update(
line.geometry.attributes.position,
2021-04-08 23:05:52 +00:00
point.geometry.attributes.position,
_p1, _p2
2021-04-05 03:52:17 +00:00
)
2021-04-04 09:36:41 +00:00
2021-04-13 06:41:05 +00:00
2021-04-04 04:54:27 +00:00
sc.render()
}
2021-04-04 09:36:41 +00:00
}
2021-04-05 08:05:53 +00:00
export function setDimLines() {
const restoreLabels = this.labelContainer.childElementCount == 0;
const dims = this.obj3d.children[1].children
2021-04-04 09:36:41 +00:00
2021-04-05 08:05:53 +00:00
let point, dist;
for (let i = 0; i < dims.length; i += 2) {
if (restoreLabels) {
point = dims[i + 1] // point node is at i+1
dist = this.constraints.get(point.name)[1]
point.label = document.createElement('div');
point.label.textContent = dist.toFixed(3);
point.label.contentEditable = true;
this.labelContainer.append(point.label)
2021-04-08 23:05:52 +00:00
2021-04-06 04:52:19 +00:00
point.label.addEventListener('focus', this.updateDim(this.c_id))
2021-04-05 08:05:53 +00:00
}
2021-04-06 21:10:07 +00:00
ids = dims[i].userData.ids
2021-04-04 09:36:41 +00:00
2021-04-06 21:10:07 +00:00
_p1 = this.obj3d.children[this.objIdx.get(ids[0])].geometry.attributes.position.array
_p2 = this.obj3d.children[this.objIdx.get(ids[1])].geometry.attributes.position.array
2021-04-05 08:05:53 +00:00
2021-04-05 03:52:17 +00:00
update(
2021-04-05 08:05:53 +00:00
dims[i].geometry.attributes.position,
2021-04-08 23:05:52 +00:00
dims[i + 1].geometry.attributes.position,
_p1,
2021-04-13 06:41:05 +00:00
_p2,
2021-04-13 07:01:28 +00:00
dims[i + 1].userData.offset
2021-04-05 03:52:17 +00:00
)
}
2021-04-04 09:36:41 +00:00
2021-04-05 03:52:17 +00:00
}
2021-04-04 09:36:41 +00:00
2021-04-09 02:43:14 +00:00
2021-04-13 06:41:05 +00:00
export function onDimMoveEnd(point) {
if (hyp2) {
point.userData.offset = hyp2.toArray() // save offset vector from hyp2
} else {
point.userData.offset = vecArr[5].clone().sub(vecArr[2]).toArray()
}
}
2021-04-09 02:43:14 +00:00
const p1 = new THREE.Vector2()
let mdpt
const p1x = new THREE.Vector2()
const p2 = new THREE.Vector2()
const p3 = new THREE.Vector2()
let disp, hyp1, hyp2
let proj, proj1, proj2
let p1e, p2e
let p1eArr, p2eArr, p3Arr
let dir, linedir, perpOffset
let dp1e, dp2e, dp12
2021-04-13 06:41:05 +00:00
function updatex(linegeom, pointgeom, _p1, _p2, offset) {
if (offset) {
if (_p1.length < _p2.length) { // corner case when p1 is pt and p2 is line
p3.set(_p1[0] + offset[0], _p1[1] + offset[1])
} else {
p3.set(_p2[0] + offset[0], _p2[1] + offset[1])
}
}
2021-04-08 23:05:52 +00:00
if (_p1.length == _p2.length) {
p1.set(_p1[0], _p1[1])
p2.set(_p2[0], _p2[1])
dir = p2.clone().sub(p1).normalize()
2021-04-09 01:28:56 +00:00
hyp2 = p3.clone().sub(p2) // note that this value is used to calculate tag-p2 offset
proj = dir.multiplyScalar(hyp2.dot(dir))
2021-04-09 02:43:14 +00:00
perpOffset = hyp2.clone().sub(proj)
2021-04-04 09:36:41 +00:00
2021-04-09 02:43:14 +00:00
p1e = p1.clone().add(perpOffset)
2021-04-08 23:05:52 +00:00
p1eArr = p1e.toArray()
2021-04-09 02:43:14 +00:00
p2e = p2.clone().add(perpOffset)
2021-04-08 23:05:52 +00:00
p2eArr = p2e.toArray()
p3Arr = p3.toArray()
2021-04-04 09:36:41 +00:00
2021-04-08 23:05:52 +00:00
dp1e = p1e.distanceToSquared(p3)
dp2e = p2e.distanceToSquared(p3)
dp12 = p1e.distanceToSquared(p2e)
2021-04-04 09:36:41 +00:00
2021-04-08 23:05:52 +00:00
linegeom.array.set(p1.toArray(), 0)
} else {
2021-04-09 01:28:56 +00:00
if (_p1.length > _p2.length) { // when p1 is line, p2 is point
2021-04-08 23:05:52 +00:00
p1.set(_p1[0], _p1[1])
p1x.set(_p1[3], _p1[4])
p2.set(_p2[0], _p2[1])
2021-04-09 01:28:56 +00:00
} else { // when p1 is point, p2 is line
p1.set(_p2[0], _p2[1])
p1x.set(_p2[3], _p2[4])
p2.set(_p1[0], _p1[1])
2021-04-08 23:05:52 +00:00
}
2021-04-09 02:43:14 +00:00
linedir = p1x.clone().sub(p1)
mdpt = p1.clone().addScaledVector(linedir, 0.5)
linedir.normalize()
2021-04-08 23:05:52 +00:00
2021-04-09 01:28:56 +00:00
disp = p2.clone().sub(mdpt)
2021-04-09 02:43:14 +00:00
proj = linedir.multiplyScalar(disp.dot(linedir))
2021-04-08 23:05:52 +00:00
2021-04-09 02:43:14 +00:00
dir = disp.clone().sub(proj)
dp12 = dir.lengthSq()
dir.normalize()
2021-04-04 09:36:41 +00:00
2021-04-08 23:05:52 +00:00
hyp1 = p3.clone().sub(mdpt)
2021-04-09 02:43:14 +00:00
proj1 = dir.clone().multiplyScalar(hyp1.dot(dir))
2021-04-08 23:05:52 +00:00
2021-04-09 01:28:56 +00:00
hyp2 = p3.clone().sub(p2) // note that this value is used to calculate tag-p2 offset
2021-04-09 02:43:14 +00:00
proj2 = dir.clone().multiplyScalar(hyp2.dot(dir))
2021-04-08 23:05:52 +00:00
p1eArr = p3.clone().sub(proj1).toArray()
p2eArr = p3.clone().sub(proj2).toArray()
p3Arr = p3.toArray()
dp1e = proj1.lengthSq()
dp2e = proj2.lengthSq()
linegeom.array.set(mdpt.toArray(), 0)
}
linegeom.array.set(p1eArr, 3)
linegeom.array.set(p1eArr, 6)
linegeom.array.set(p2eArr, 9)
linegeom.array.set(p2eArr, 12)
linegeom.array.set(p2.toArray(), 15)
if (dp12 >= dp1e && dp12 >= dp2e) {
linegeom.array.set(p3Arr, 18)
} else {
if (dp1e > dp2e) {
linegeom.array.set(p2eArr, 18)
} else {
linegeom.array.set(p1eArr, 18)
}
}
linegeom.array.set(p3Arr, 21)
2021-04-04 09:36:41 +00:00
2021-04-05 03:52:17 +00:00
linegeom.needsUpdate = true;
2021-04-04 09:36:41 +00:00
2021-04-08 23:05:52 +00:00
pointgeom.array.set(p3Arr)
2021-04-05 03:52:17 +00:00
pointgeom.needsUpdate = true;
2021-04-08 23:05:52 +00:00
2021-04-13 06:41:05 +00:00
}
const divisions = 12
export function findIntersection(q, s, p, r) {
/*
Based on: https://stackoverflow.com/questions/563198/
q+s p+r
\/__________ q+u*s
/\
/ \
p q
u = (q p) × r / (r × s)
when r × s = 0, the lines are either colinear or parallel
function returns u
for "real" intersection to exist, 0<u<1
*/
const q_minus_p = q.clone().sub(p);
const r_cross_s = r.cross(s);
if (r_cross_s === 0) return null; //either colinear or parallel
return q_minus_p.cross(r) / r_cross_s;
}
const vecArr = Array(6)
for (var i = 0; i < vecArr.length; i++) vecArr[i] = new THREE.Vector2();
const a = Array(3)
function update(linegeom, pointgeom, _l1, _l2, offset) {
let i = 0;
for (; i < 4;) {
const arr = i == 0 ? _l1 : _l2
vecArr[i++].set(arr[0], arr[1])
vecArr[i++].set(arr[3] - arr[0], arr[4] - arr[1])
}
const centerScalar = findIntersection(...vecArr.slice(0, 4))
const center = vecArr[4].addVectors(vecArr[0], vecArr[1].clone().multiplyScalar(centerScalar))
if (offset) {
p3.set(center.x + offset[0], center.y + offset[1])
}
vecArr[5].subVectors(p3, center)
const tagRadius = vecArr[5].length()
/*
if tag is more than 90 deg away from midline, we shift everything by 180
a: array that describes absolute angular position of angle start, angle end, and tag
a[2]:
tag a[1]:angle end
\ | /
\ | /
___\|/___ a[0]+dA/2:midline
/ \
/ \
/ \
a[0]:angle start
*/
for (let j = 1, i = 0; j < vecArr.length; j += 2, i++) {
a[i] = Math.atan2(vecArr[j].y, vecArr[j].x)
}
let dA = unreflex(a[1] - a[0])
let tagtoMidline = unreflex(a[2] - (a[0] + dA / 2))
let shift = Math.abs(tagtoMidline) < Math.PI / 2 ? 0 : Math.PI;
let tA1 = unreflex(a[2] - (a[0] + shift))
let tA2 = unreflex(a[2] - (a[0] + dA + shift))
let a1, deltaAngle;
if (dA * tA1 < 0) {
a1 = a[0] + tA1 + shift
deltaAngle = dA - tA1
} else if (dA * tA2 > 0) {
a1 = a[0] + shift
deltaAngle = dA + tA2
} else {
a1 = a[0] + shift
deltaAngle = dA
}
let points = linegeom.array
let d = 0;
points[d++] = center.x + tagRadius * Math.cos(a1)
points[d++] = center.y + tagRadius * Math.sin(a1)
d++
const angle = a1 + (1 / divisions) * deltaAngle
points[d++] = center.x + tagRadius * Math.cos(angle)
points[d++] = center.y + tagRadius * Math.sin(angle)
d++
for (i = 2; i <= divisions; i++) {
points[d++] = points[d - 4]
points[d++] = points[d - 4]
d++
const angle = a1 + (i / divisions) * deltaAngle
points[d++] = center.x + tagRadius * Math.cos(angle)
points[d++] = center.y + tagRadius * Math.sin(angle)
d++
}
for (i = 0; i < 2; i++) {
points[d++] = vecArr[2 * i].x
points[d++] = vecArr[2 * i].y
d++
points[d++] = center.x + tagRadius * Math.cos(a[i] + shift)
points[d++] = center.y + tagRadius * Math.sin(a[i] + shift)
d++
}
linegeom.needsUpdate = true;
pointgeom.array.set(p3.toArray())
pointgeom.needsUpdate = true;
2021-04-05 08:05:53 +00:00
}
2021-04-13 06:41:05 +00:00
const twoPi = Math.PI * 2
const negTwoPi = - Math.PI * 2
const negPi = - Math.PI
function unreflex(angle) {
if (angle > Math.PI) {
angle = negTwoPi + angle
} else if (angle < negPi) {
angle = twoPi + angle
}
return angle
}
const getAngle = (Obj3dLines) => {
for (let i = 0; i < 2; i++) {
const arr = Obj3dLines[i].geometry.attributes.position.array
vecArr[2 * i].set(...arr.slice(0, 2))
vecArr[2 * i + 1].set(arr[3] - arr[0], arr[4] - arr[1])
}
const a1 = Math.atan2(vecArr[1].y, vecArr[1].x)
const a2 = Math.atan2(vecArr[3].y, vecArr[3].x)
let deltaAngle = Math.abs(a2 - a1)
if (deltaAngle > Math.PI) {
deltaAngle = Math.PI * 2 - deltaAngle
}
return deltaAngle / Math.PI * 180
}