576 lines
15 KiB
JavaScript
576 lines
15 KiB
JavaScript
import * as THREE from '../node_modules/three/src/Three';
|
|
import { color } from './shared'
|
|
|
|
const lineMaterial = new THREE.LineBasicMaterial({
|
|
linewidth: 1,
|
|
color: color.dimension,
|
|
})
|
|
|
|
|
|
const pointMaterial = new THREE.PointsMaterial({
|
|
color: color.dimension,
|
|
size: 4,
|
|
})
|
|
|
|
let dimVal
|
|
export async function drawDimension() {
|
|
let selection = await this.awaitSelection({ point: 2 }, { point: 1, line: 1 }, { line: 2 })
|
|
if (selection == null) return;
|
|
|
|
let line, constraint, dimType
|
|
if (selection.every(e => e.userData.type == 'line')) {
|
|
line = new THREE.LineSegments(
|
|
new THREE.BufferGeometry().setAttribute('position',
|
|
new THREE.Float32BufferAttribute(Array((divisions + 2) * 2 * 3).fill(-0.001), 3)
|
|
),
|
|
lineMaterial.clone()
|
|
);
|
|
|
|
dimVal = getAngle(selection)
|
|
|
|
constraint = [
|
|
'angle', dimVal,
|
|
[-1, -1, selection[0].name, selection[1].name], 0
|
|
]
|
|
|
|
dimType = 'a'
|
|
} else {
|
|
line = new THREE.LineSegments(
|
|
new THREE.BufferGeometry().setAttribute('position',
|
|
new THREE.Float32BufferAttribute(Array(3 * 8).fill(-0.001), 3)
|
|
),
|
|
lineMaterial.clone()
|
|
);
|
|
|
|
let ptLineOrder
|
|
if (selection.every(e => e.userData.type == 'point')) {
|
|
dimVal = 0;
|
|
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)
|
|
|
|
constraint = [
|
|
'pt_pt_distance', dimVal,
|
|
// 'smart_dist', dimVal,
|
|
[selection[0].name, selection[1].name, -1, -1]
|
|
]
|
|
|
|
} 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])
|
|
tagPos.set(ptArr[0], ptArr[1])
|
|
dir = p2.clone().sub(p1).normalize()
|
|
disp = tagPos.clone().sub(p1)
|
|
proj = dir.multiplyScalar(disp.dot(dir))
|
|
perpOffset = disp.clone().sub(proj)
|
|
dimVal = Math.sign(perpOffset.y)*Math.sqrt(perpOffset.x ** 2 + perpOffset.y ** 2)
|
|
|
|
constraint = [
|
|
'pt_line_distance', dimVal,
|
|
[selection[ptLineOrder[0]].name, -1, selection[ptLineOrder[1]].name, -1]
|
|
]
|
|
}
|
|
|
|
|
|
dimType = 'd'
|
|
}
|
|
|
|
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.userData.type = 'dimension'
|
|
line.userData.dimType = dimType
|
|
point.userData.type = 'dimension'
|
|
point.userData.dimType = dimType
|
|
line.layers.enable(2)
|
|
point.layers.enable(2)
|
|
|
|
this.dimGroup.add(line).add(point)
|
|
const onMove = this._onMoveDimension(point, line, true)
|
|
point.label = document.createElement('div');
|
|
point.label.textContent = dimVal.toFixed(3);
|
|
point.label.contentEditable = true;
|
|
this.labelContainer.append(point.label)
|
|
|
|
let onEnd, onKey;
|
|
let add = await new Promise((res) => {
|
|
onEnd = () => {
|
|
if (point.userData.dimType == 'a') {
|
|
point.userData.offset = vecArr[5].toArray()
|
|
} else {
|
|
point.userData.offset = hyp2.toArray() // save offset vector from hyp2
|
|
}
|
|
res(true)
|
|
}
|
|
onKey = (e) => e.key == 'Escape' && res(false)
|
|
|
|
this.canvas.addEventListener('pointermove', onMove)
|
|
this.canvas.addEventListener('pointerdown', onEnd)
|
|
window.addEventListener('keydown', onKey)
|
|
})
|
|
|
|
this.canvas.removeEventListener('pointermove', onMove)
|
|
this.canvas.removeEventListener('pointerdown', onEnd)
|
|
window.removeEventListener('keydown', onKey)
|
|
point.geometry.computeBoundingSphere()
|
|
line.geometry.computeBoundingSphere()
|
|
|
|
if (add) {
|
|
|
|
if (line.userData.dimType == 'h') {
|
|
constraint[0] = 'h_dist'
|
|
constraint[1] = p2.x - p1.x
|
|
} else if (line.userData.dimType == 'v') {
|
|
constraint[0] = 'v_dist'
|
|
constraint[1] = p2.y - p1.y
|
|
}
|
|
|
|
this.constraints.set(++this.c_id, constraint)
|
|
|
|
selection[0].userData.constraints.push(this.c_id)
|
|
selection[1].userData.constraints.push(this.c_id)
|
|
|
|
this.updateOtherBuffers()
|
|
line.name = this.c_id
|
|
|
|
|
|
point.name = this.c_id
|
|
point.label.addEventListener('focus', this.updateDim(this.c_id))
|
|
|
|
} else {
|
|
|
|
this.dimGroup.children.splice(this.dimGroup.children.length - 2, 2).forEach(
|
|
e => {
|
|
e.geometry.dispose()
|
|
e.material.dispose()
|
|
}
|
|
)
|
|
this.labelContainer.removeChild(this.labelContainer.lastChild);
|
|
this.scene.render()
|
|
}
|
|
if (this.scene.mode == "dimension") {
|
|
this.drawDimension()
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
export function updateDim(c_id) {
|
|
return (ev_focus) => {
|
|
let value = ev_focus.target.textContent
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key == 'Enter') {
|
|
e.preventDefault()
|
|
const ent = this.constraints.get(c_id)
|
|
ent[1] = parseFloat(ev_focus.target.textContent)
|
|
value = ent[1]
|
|
this.constraints.set(c_id, ent)
|
|
this.updateOtherBuffers()
|
|
this.solve()
|
|
this.scene.render()
|
|
ev_focus.target.blur()
|
|
this.updateBoundingSpheres()
|
|
} else if (e.key == 'Escape') {
|
|
ev_focus.target.textContent = value
|
|
getSelection().empty()
|
|
ev_focus.target.blur()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
const tagPos = new THREE.Vector2()
|
|
let ids
|
|
export function _onMoveDimension(point, line, initial) {
|
|
|
|
ids = line.userData.ids
|
|
|
|
let _p1 = this.obj3d.children[this.objIdx.get(ids[0])].geometry.attributes.position.array
|
|
let _p2 = this.obj3d.children[this.objIdx.get(ids[1])].geometry.attributes.position.array
|
|
|
|
let loc;
|
|
|
|
let update;
|
|
if (line.userData.dimType == 'a') {
|
|
update = updateAngle
|
|
} else {
|
|
update = updateDistance
|
|
}
|
|
|
|
return (e) => {
|
|
loc = this.getLocation(e)
|
|
|
|
tagPos.set(loc.x, loc.y)
|
|
|
|
update(
|
|
line,
|
|
point,
|
|
_p1, _p2, null, initial
|
|
)
|
|
this.scene.render()
|
|
}
|
|
}
|
|
|
|
export function setDimLines() {
|
|
const restoreLabels = this.labelContainer.childElementCount == 0;
|
|
const dims = this.dimGroup.children
|
|
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)
|
|
point.label.addEventListener('focus', this.updateDim(this.c_id))
|
|
}
|
|
|
|
ids = dims[i].userData.ids
|
|
|
|
let _p1 = this.obj3d.children[this.objIdx.get(ids[0])].geometry.attributes.position.array
|
|
let _p2 = this.obj3d.children[this.objIdx.get(ids[1])].geometry.attributes.position.array
|
|
|
|
let update;
|
|
if (dims[i].userData.dimType == 'a') {
|
|
update = updateAngle
|
|
} else {
|
|
update = updateDistance
|
|
}
|
|
|
|
update(
|
|
// dims[i].geometry.attributes.position,
|
|
// dims[i + 1].geometry.attributes.position,
|
|
dims[i],
|
|
dims[i + 1],
|
|
_p1,
|
|
_p2,
|
|
dims[i + 1].userData.offset
|
|
)
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const p1 = new THREE.Vector2()
|
|
let mdpt
|
|
const p1x = new THREE.Vector2()
|
|
const p2 = new THREE.Vector2()
|
|
let disp, hyp1, hyp2
|
|
let proj, proj1, proj2
|
|
let p1e, p2e
|
|
let p1eArr, p2eArr, tagPosArr
|
|
let dir, linedir, perpOffset
|
|
let dp1e, dp2e, dp12
|
|
|
|
function updateDistance(line, point, _p1, _p2, offset, initial) {
|
|
|
|
const linegeom = line.geometry.attributes.position
|
|
const pointgeom = point.geometry.attributes.position
|
|
|
|
if (offset) {
|
|
if (_p1.length < _p2.length) { // corner case when p1 is pt and p2 is line
|
|
tagPos.set(_p1[0] + offset[0], _p1[1] + offset[1])
|
|
} else {
|
|
tagPos.set(_p2[0] + offset[0], _p2[1] + offset[1])
|
|
}
|
|
}
|
|
|
|
let phantom = null
|
|
if (_p1.length == _p2.length) {
|
|
p1.set(_p1[0], _p1[1])
|
|
p2.set(_p2[0], _p2[1])
|
|
|
|
if (initial) {
|
|
if (
|
|
(tagPos.x - p1.x) * (tagPos.x - p2.x) > 0 &&
|
|
(tagPos.y - p1.y) * (tagPos.y - p2.y) < 0
|
|
) {
|
|
line.userData.dimType = 'v'
|
|
point.userData.dimType = 'v'
|
|
// point.label.textContent = Math.abs(p1.y - p2.y).toFixed(3);
|
|
point.label.textContent = (p2.y - p1.y).toFixed(3);
|
|
} else if (
|
|
(tagPos.x - p1.x) * (tagPos.x - p2.x) < 0 &&
|
|
(tagPos.y - p1.y) * (tagPos.y - p2.y) > 0
|
|
) {
|
|
line.userData.dimType = 'h'
|
|
point.userData.dimType = 'h'
|
|
// point.label.textContent = Math.abs(p1.x - p2.x).toFixed(3);
|
|
point.label.textContent = (p2.x - p1.x).toFixed(3);
|
|
} else {
|
|
line.userData.dimType = 'd'
|
|
point.userData.dimType = 'd'
|
|
point.label.textContent = dimVal.toFixed(3);
|
|
}
|
|
}
|
|
|
|
|
|
switch (line.userData.dimType) {
|
|
case 'v':
|
|
phantom = [_p1[0] + 1, _p1[1]]
|
|
break;
|
|
case 'h':
|
|
phantom = [_p1[0], _p1[1] + 1]
|
|
break;
|
|
default:
|
|
dir = p2.clone().sub(p1).normalize()
|
|
hyp2 = tagPos.clone().sub(p2) // note that this value is used to calculate tag-p2 offset
|
|
proj = dir.multiplyScalar(hyp2.dot(dir))
|
|
perpOffset = hyp2.clone().sub(proj)
|
|
|
|
p1e = p1.clone().add(perpOffset)
|
|
p1eArr = p1e.toArray()
|
|
p2e = p2.clone().add(perpOffset)
|
|
p2eArr = p2e.toArray()
|
|
tagPosArr = tagPos.toArray()
|
|
|
|
dp1e = p1e.distanceToSquared(tagPos)
|
|
dp2e = p2e.distanceToSquared(tagPos)
|
|
dp12 = p1e.distanceToSquared(p2e)
|
|
linegeom.array.set(p1.toArray(), 0)
|
|
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
if (_p1.length != _p2.length || phantom) {
|
|
if (phantom) {
|
|
p1.set(_p1[0], _p1[1])
|
|
p1x.set(...phantom)
|
|
p2.set(_p2[0], _p2[1])
|
|
} else if (_p1.length > _p2.length) { // when p1 is line, p2 is point
|
|
p1.set(_p1[0], _p1[1])
|
|
p1x.set(_p1[3], _p1[4])
|
|
p2.set(_p2[0], _p2[1])
|
|
} 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])
|
|
}
|
|
|
|
linedir = p1x.clone().sub(p1)
|
|
mdpt = p1.clone().addScaledVector(linedir, 0.5)
|
|
linedir.normalize()
|
|
|
|
disp = p2.clone().sub(mdpt)
|
|
proj = linedir.multiplyScalar(disp.dot(linedir))
|
|
|
|
dir = disp.clone().sub(proj)
|
|
dp12 = dir.lengthSq()
|
|
dir.normalize()
|
|
|
|
hyp1 = tagPos.clone().sub(mdpt)
|
|
proj1 = dir.clone().multiplyScalar(hyp1.dot(dir))
|
|
|
|
hyp2 = tagPos.clone().sub(p2) // note that this value is used to calculate tag-p2 offset
|
|
proj2 = dir.clone().multiplyScalar(hyp2.dot(dir))
|
|
|
|
p1eArr = tagPos.clone().sub(proj1).toArray()
|
|
p2eArr = tagPos.clone().sub(proj2).toArray()
|
|
tagPosArr = tagPos.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(tagPosArr, 18)
|
|
} else {
|
|
if (dp1e > dp2e) {
|
|
linegeom.array.set(p2eArr, 18)
|
|
} else {
|
|
linegeom.array.set(p1eArr, 18)
|
|
}
|
|
}
|
|
linegeom.array.set(tagPosArr, 21)
|
|
|
|
linegeom.needsUpdate = true;
|
|
|
|
pointgeom.array.set(tagPosArr)
|
|
pointgeom.needsUpdate = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
const divisions = 12
|
|
const vecArr = Array(6)
|
|
for (let k = 0; k < vecArr.length; k++) vecArr[k] = new THREE.Vector2();
|
|
const a = Array(3)
|
|
const _vec2 = new THREE.Vector2()
|
|
let arr, i, j, centerScalar, r_cross_s, center, tagRadius
|
|
let dA, tagtoMidline, shift, tA1, tA2, a1, deltaAngle;
|
|
|
|
function updateAngle(line, point, _l1, _l2, offset) {
|
|
/*
|
|
l2:[x0,y0,z0,x1,y1,z1]
|
|
/
|
|
tagPos:tag-""/-.
|
|
| \
|
|
vecArr[5][1]-->|___. _|__ l1:[x0,y0,z0,x1,y1,z1]
|
|
vecArr[5][0]----^ ^--center
|
|
|
|
vecArr = [
|
|
0: _l1 origin
|
|
1: _l1 disp
|
|
2: _l2 origin
|
|
3: _l2 disp
|
|
4: center
|
|
5: tag offset from center
|
|
]
|
|
*/
|
|
|
|
const linegeom = line.geometry.attributes.position
|
|
const pointgeom = point.geometry.attributes.position
|
|
|
|
for (i = 0; i < 4;) {
|
|
arr = i == 0 ? _l1 : _l2
|
|
vecArr[i++].set(arr[0], arr[1])
|
|
vecArr[i++].set(arr[3] - arr[0], arr[4] - arr[1])
|
|
}
|
|
|
|
// https://stackoverflow.com/questions/563198/
|
|
r_cross_s = vecArr[3].cross(vecArr[1]);
|
|
if (r_cross_s === 0) {
|
|
centerScalar = 0.5
|
|
} else {
|
|
centerScalar = _vec2.subVectors(vecArr[0], vecArr[2]).cross(vecArr[3]) / r_cross_s;
|
|
}
|
|
center = vecArr[4].addVectors(vecArr[0], vecArr[1].clone().multiplyScalar(centerScalar))
|
|
|
|
if (offset) {
|
|
tagPos.set(center.x + offset[0], center.y + offset[1])
|
|
}
|
|
|
|
vecArr[5].subVectors(tagPos, center) // tag offset
|
|
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 (j = 1, i = 0; j < vecArr.length; j += 2, i++) {
|
|
a[i] = Math.atan2(vecArr[j].y, vecArr[j].x)
|
|
}
|
|
|
|
dA = unreflex(a[1] - a[0])
|
|
tagtoMidline = unreflex(a[2] - (a[0] + dA / 2))
|
|
shift = Math.abs(tagtoMidline) < Math.PI / 2 ? 0 : Math.PI;
|
|
tA1 = unreflex(a[2] - (a[0] + shift))
|
|
tA2 = unreflex(a[2] - (a[0] + dA + shift))
|
|
|
|
if (dA * tA1 < 0) { // if dA and tA1 are not the same sign
|
|
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
|
|
}
|
|
|
|
j = 0;
|
|
linegeom.array[j++] = center.x + tagRadius * Math.cos(a1)
|
|
linegeom.array[j++] = center.y + tagRadius * Math.sin(a1)
|
|
j++
|
|
let angle = a1 + (1 / divisions) * deltaAngle
|
|
linegeom.array[j++] = center.x + tagRadius * Math.cos(angle)
|
|
linegeom.array[j++] = center.y + tagRadius * Math.sin(angle)
|
|
j++
|
|
for (i = 2; i <= divisions; i++) {
|
|
linegeom.array[j++] = linegeom.array[j - 4]
|
|
linegeom.array[j++] = linegeom.array[j - 4]
|
|
j++
|
|
angle = a1 + (i / divisions) * deltaAngle
|
|
linegeom.array[j++] = center.x + tagRadius * Math.cos(angle)
|
|
linegeom.array[j++] = center.y + tagRadius * Math.sin(angle)
|
|
j++
|
|
}
|
|
for (i = 0; i < 2; i++) {
|
|
linegeom.array[j++] = vecArr[2 * i].x
|
|
linegeom.array[j++] = vecArr[2 * i].y
|
|
j++
|
|
linegeom.array[j++] = center.x + tagRadius * Math.cos(a[i] + shift)
|
|
linegeom.array[j++] = center.y + tagRadius * Math.sin(a[i] + shift)
|
|
j++
|
|
}
|
|
|
|
linegeom.needsUpdate = true;
|
|
pointgeom.array.set(tagPos.toArray())
|
|
pointgeom.needsUpdate = true;
|
|
}
|
|
|
|
|
|
|
|
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
|
|
}
|
|
|
|
|
|
export function onDimMoveEnd(point) {
|
|
if (point.userData.dimType == 'a') {
|
|
point.userData.offset = vecArr[5].toArray()
|
|
} else {
|
|
point.userData.offset = hyp2.toArray() // save offset vector from hyp2
|
|
}
|
|
} |