semi working angle

master
howard 2021-04-12 18:37:16 -07:00
parent 67d6bf8090
commit 8aeb5409c4
10 changed files with 500 additions and 42 deletions

View File

@ -111,8 +111,8 @@ export class Scene {
helpersGroup.add(this.axes);
const dist = 50
const light1 = new THREE.PointLight(color.lighting, 0.7);
const dist = 15
const light1 = new THREE.PointLight(color.lighting, 0.6);
light1.position.set(dist, dist, dist);
helpersGroup.add(light1);
const light2 = new THREE.PointLight(color.lighting, 0.6);
@ -292,7 +292,7 @@ export class Scene {
}
subtract(m1, m2) {
subtract(m1, m2, op) {
let bspA = CSG.fromMesh(m1)
let bspB = CSG.fromMesh(m2)
m1.visible = false
@ -302,13 +302,30 @@ export class Scene {
// // Subtract one bsp from the other via .subtract... other supported modes are .union and .intersect
let bspResult = bspA.subtract(bspB)
let bspResult, opChar;
switch (op) {
case 's':
bspResult = bspA.subtract(bspB)
opChar = "-"
break;
case 'u':
bspResult = bspA.union(bspB)
opChar = "\u222a"
break;
case 'i':
bspResult = bspA.intersect(bspB)
opChar = "\u2229"
break;
default:
break;
}
// //Get the resulting mesh from the result bsp, and assign meshA.material to the resulting mesh
let mesh = CSG.toMesh(bspResult, m1.matrix, m1.material)
mesh.userData.type = 'mesh'
mesh.name = `${m1.name}-${m2.name}`
mesh.name = `(${m1.name}${opChar}${m2.name})`
mesh.layers.enable(1)

View File

@ -11,6 +11,7 @@ import { get3PtArc } from './drawArc'
import { replacer, reviver } from './utils'
import { AxesHelper } from './sketchAxes'
import { drawDimension, _onMoveDimension, setDimLines, updateDim } from './drawDimension';
import { drawAngle, _onMoveAngle, setAngLines, updateAng } from './drawAngle';
@ -128,9 +129,13 @@ class Sketch {
this.drawOnClick2 = drawOnClick2.bind(this);
this.drawDimension = drawDimension.bind(this)
this.drawAngle = drawAngle.bind(this)
this._onMoveDimension = _onMoveDimension.bind(this)
this._onMoveAngle = _onMoveAngle.bind(this)
this.setDimLines = setDimLines.bind(this)
this.setAngLines = setAngLines.bind(this)
this.updateDim = updateDim.bind(this)
this.updateAng = updateAng.bind(this)
this.awaitSelection = awaitSelection.bind(this);
@ -221,6 +226,10 @@ class Sketch {
this.drawDimension()
this.mode = ""
break;
case 'q':
this.drawAngle()
this.mode = ""
break;
case 'p':
this.canvas.addEventListener('pointerdown', this.drawOnClick1)
this.mode = "point"
@ -457,6 +466,7 @@ class Sketch {
arcs were not updated in above loop, we go through all arcs linkedObjs
and updated based on the control pts (which were updated in loop above)
*/
for (let [k, obj] of this.linkedObjs) {
if (obj[0] != 'arc') continue;
const [p1, p2, c, arc] = obj[1].map(e => this.obj3d.children[this.objIdx.get(e)])
@ -472,7 +482,8 @@ class Sketch {
}
this.setDimLines()
// this.setDimLines()
this.setAngLines()
this.obj3d.dispatchEvent({ type: 'change' })
}

395
src/drawAngle.js Normal file
View File

@ -0,0 +1,395 @@
import * as THREE from '../node_modules/three/src/Three';
import { color } from './shared'
const lineMaterial = new THREE.LineBasicMaterial({
linewidth: 2,
color: color.dimension,
})
const pointMaterial = new THREE.PointsMaterial({
color: color.dimension,
size: 4,
})
const divisions = 12
export async function drawAngle() {
let selection = await this.awaitSelection({ line: 2 })
if (selection == null) return;
const line = new THREE.LineSegments(
new THREE.BufferGeometry().setAttribute('position',
new THREE.Float32BufferAttribute(Array((divisions + 2) * 2 * 3).fill(-0.001), 3)
),
lineMaterial.clone()
);
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)
let angle = getAngle(selection)
this.obj3d.children[1].add(line).add(point)
const onMove = this._onMoveAngle(point, line)
point.label = document.createElement('div');
point.label.textContent = angle.toFixed(3);
point.label.contentEditable = true;
this.labelContainer.append(point.label)
let onEnd, onKey;
let add = await new Promise((res) => {
onEnd = (e) => 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) {
this.constraints.set(++this.c_id,
[
'angle', angle,
[-1, -1, selection[0].name, selection[1].name]
]
)
selection[0].userData.constraints.push(this.c_id)
selection[1].userData.constraints.push(this.c_id)
this.updateOtherBuffers()
line.name = this.c_id
line.userData.type = 'dimension'
point.name = this.c_id
point.userData.type = 'dimension'
point.label.addEventListener('focus', this.updateAng(this.c_id))
} else {
this.obj3d.children[1].children.splice(this.obj3d.children[1].length - 2, 2).forEach(
e => {
e.geometry.dispose()
e.material.dispose()
}
)
this.labelContainer.removeChild(this.labelContainer.lastChild);
sc.render()
}
return
}
export function updateAng(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()
sc.render()
ev_focus.target.blur()
this.updateBoundingSpheres()
} else if (e.key == 'Escape') {
ev_focus.target.textContent = value
getSelection().empty()
ev_focus.target.blur()
}
})
}
}
let ids, _l1, _l2
export function _onMoveAngle(point, line) {
ids = line.userData.ids
_l1 = this.obj3d.children[this.objIdx.get(ids[0])].geometry.attributes.position.array
_l2 = this.obj3d.children[this.objIdx.get(ids[1])].geometry.attributes.position.array
let loc;
return (e) => {
loc = this.getLocation(e)
p3.set(loc.x, loc.y)
update(
line.geometry.attributes.position,
point.geometry.attributes.position,
_l1, _l2
)
// point.userData.offset = tagOffset.toArray() // save offset vector from center
point.userData.offset = tagOffset // save offset vector from center
tagOffset = undefined
sc.render()
}
}
export function setAngLines() {
const restoreLabels = this.labelContainer.childElementCount == 0;
const dims = this.obj3d.children[1].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.updateAng(this.c_id))
}
ids = dims[i].userData.ids
_l1 = this.obj3d.children[this.objIdx.get(ids[0])].geometry.attributes.position.array
_l2 = this.obj3d.children[this.objIdx.get(ids[1])].geometry.attributes.position.array
tagOffset = dims[i + 1].userData.offset
update(
dims[i].geometry.attributes.position,
dims[i + 1].geometry.attributes.position,
_l1,
_l2
)
}
}
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;
}
/*
_l2:[x0,y0,z0,x1,y1,z1]
/
p3:tag-""/-.
tagOffset[1]-->| \
|__ . _|__ _l1:[x0,y0,z0,x1,y1,z1]
tagOffset[0]----^ ^--center
vecArr = [
0: _l1 origin
1: _l1 disp
2: _l2 origin
3: _l2 disp
4: center
5: tag disp from center
]
*/
const vecArr = Array(6)
for (var i = 0; i < vecArr.length; i++) vecArr[i] = new THREE.Vector2();
const a = Array(3)
const p3 = new THREE.Vector2()
let tagOffset
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
}
function update(linegeom, pointgeom, _l1, _l2) {
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[i++].addVectors(vecArr[0], vecArr[1].clone().multiplyScalar(centerScalar))
// tagOffset = vecArr[i++].subVectors(p3, center)
if (tagOffset === undefined) {
tagOffset = vecArr[i++].subVectors(p3, center)
// } else if (Array.isArray(tagOffset)) {
// tagOffset = new THREE.Vector2(tagOffset[0],tagOffset[1])
} else {
p3.addVectors(center, tagOffset)
}
// console.log(p3, center, 'vr')
// console.log(vecArr, 'vecArr')
// console.log(tagOffset, xx)
// console.log(tagOffset.length())
const tagRadius = tagOffset.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;
}
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
}

View File

@ -83,9 +83,38 @@ export function get3PtArc(p1, p2, c, divisions = n) {
const radius = Math.sqrt(v1[0] ** 2 + v1[1] ** 2)
let deltaAngle = a2 - a1
if (deltaAngle <=0) deltaAngle += Math.PI*2
// console.log(deltaAngle)
// let deltaAngle = a2 - a1
// if (deltaAngle > Math.PI ){
// deltaAngle = - Math.PI*2 + deltaAngle
// } else if (deltaAngle < -Math.PI) {
// deltaAngle = Math.PI*2 + deltaAngle
// }
// let deltaAngle = Math.abs(a2 - a1)
// if (deltaAngle > Math.PI){
// deltaAngle = Math.PI*2 - deltaAngle
// }
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 function getAngleArc(a1, a2, c, radius, divisions = n) {
let deltaAngle = a2 - a1
let points = new Float32Array((divisions + 1) * 3)

View File

@ -1,5 +1,5 @@
import * as THREE from '../node_modules/three/src/Three';
import { color, ptObj } from './shared'
import { color} from './shared'
export function extrude(sketch) {
let constraints = sketch.constraints;
@ -36,7 +36,7 @@ export function extrude(sketch) {
)
]
if (d == -1 || d == node) continue;
if (d == children[1]) {
if (d == children[4]) {
console.log('pair found')
};
findTouching(d)
@ -52,7 +52,7 @@ export function extrude(sketch) {
if (c == -1) continue;
const d = children[objIdx.get(c)]
if (d == node) continue;
if (d == children[1]) {
if (d == children[4]) {
console.log('loop found')
} else {
if (!visited.has(d)) {

View File

@ -11,7 +11,7 @@ body {
font-family: sans-serif;
overflow: hidden;
--topNavH: 48px;
--sideNavW: 200px;
--sideNavW: 240px;
}
#c {

View File

@ -15,6 +15,20 @@ export const NavBar = () => {
const treeEntries = useSelector(state => state.treeEntries)
const activeSketchId = useSelector(state => state.treeEntries.activeSketchId)
const boolOp = (code) => {
if (sc.selected.length != 2 || !sc.selected.every(e => e.userData.type == 'mesh')) return
const [m1, m2] = sc.selected
const mesh = sc.subtract(m1, m2, code)
dispatch({ type: 'rx-boolean', mesh, deps: [m1.name, m2.name] })
sc.render()
forceUpdate()
}
const extrude = () => {
console.log(treeEntries.tree[activeSketchId])
sc.extrude(treeEntries.byId[activeSketchId])
}
useEffect(() => {
if (!activeSketchId) {
sc.canvas.addEventListener('pointermove', sc.onHover)
@ -39,19 +53,10 @@ export const NavBar = () => {
}, 'Finish'] :
[FaEdit, sc.addSketch, 'Sketch [s]']
,
[FaCube, () => sc.extrude(treeEntries.byId[activeSketchId]), 'Extrude [e]'],
[Icon.Union, () => sc.extrude(treeEntries.byId[activeSketchId]), 'Union'],
[Icon.Subtract, () => {
if (sc.selected.length != 2 || !sc.selected.every(e => e.userData.type == 'mesh')) return
// console.log('here')
const [m1, m2] = sc.selected
const mesh = sc.subtract(m1, m2)
dispatch({ type: 'rx-boolean', mesh, deps: [m1.name, m2.name] })
sc.render()
forceUpdate()
}, 'Subtract'],
[Icon.Intersect, () => sc.extrude(treeEntries.byId[activeSketchId]), 'Intersect'],
[FaCube, extrude , 'Extrude [e]'],
[Icon.Union, ()=>boolOp('u'), 'Union'],
[Icon.Subtract, ()=>boolOp('s'), 'Subtract'],
[Icon.Intersect, ()=>boolOp('i'), 'Intersect'],
[Icon.Dimension, () => sc.extrude(treeEntries.byId[activeSketchId]), 'Dimension [d]'],
[Icon.Line, () => sc.extrude(treeEntries.byId[activeSketchId]), 'Line [l]'],
[Icon.Arc, () => sc.extrude(treeEntries.byId[activeSketchId]), 'Arc [a]'],

View File

@ -92,7 +92,7 @@ export function reducer(state = {}, action) {
return update(state, {
treeEntries: { $set: obj }
treeEntries: { $merge: obj }
})

View File

@ -48,12 +48,10 @@ const TreeEntry = ({ entId }) => {
const [_, forceUpdate] = useReducer(x => x + 1, 0);
// const vis = obj3d.layers.mask & 1
return <div className='btn select-none flex justify-start w-full h-7 items-center text-sm'
onDoubleClick={() => {
if (entId[0] == 's') {
if (obj3d.userData.type == 'sketch') {
activeSketchId && treeEntries[activeSketchId].deactivate()
sketch.activate()
sc.clearSelection()
@ -67,14 +65,13 @@ const TreeEntry = ({ entId }) => {
sc.render()
}}
onPointerLeave={() => {
// console.log('activeid',activeSketchId,'visstate',visState)
if (visible & entId[0] == 's') return
if (visible & obj3d.userData.type == 'sketch') return
if (sc.selected.includes(obj3d) || activeSketchId == obj3d.name) return
sc.setHover(obj3d, 0)
sc.render()
}}
onClick={() => {
if (entId[0] == 'm') {
if (obj3d.userData.type == 'mesh') {
sc.selected.push(
obj3d
)

View File

@ -14,26 +14,30 @@ boolean flesh out refresh / replace mesh
- consume skeches after extrude // done
- selection hover disspates when rehovered //fixed
- boolean unable to select click //fixed
- hover sync between tree and work area // done, punt on stretch
vertical // done
horizontal // done
- select sketch for extrusion, punt, leverage current sketch modality
- hover sync between tree and work area
- select sketch for extrusion
auto update extrude
extrude dialogue
loopfind
button panel cleanup
file save, stl export
constriant buttons ,tangent, angle
button panel cleanup
constraint angle
3 pt arc
constraint labels
reattach sketch
constraint labels, tangent
auto snap
tree ent renaming and better default names