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);
@ -152,7 +157,7 @@ class Sketch {
this.setDimLines()
this.obj3d.traverse(e=>e.layers.enable(2))
this.obj3d.traverse(e => e.layers.enable(2))
this.obj3d.visible = true
this.scene.axes.matrix = this.obj3d.matrix
this.scene.axes.visible = true
@ -168,7 +173,7 @@ class Sketch {
this.store.dispatch({ type: 'exit-sketch' })
this.labelContainer.innerHTML = ""
this.obj3d.visible = false
this.obj3d.traverse(e=>e.layers.disable(2))
this.obj3d.traverse(e => e.layers.disable(2))
this.scene.axes.visible = false
this.scene.activeSketch = null
}
@ -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"
@ -427,9 +436,9 @@ class Sketch {
this.linkedObjs.size, links_buffer)
/*
- loop to update all the children that are points
- why +6? we skip first two triplets because it refers to a non-geometry children
- we also sneak in updating lines children as well, by checking when ptsBuf[ptr] is NaN
- loop to update all the children that are points
- why +6? we skip first two triplets because it refers to a non-geometry children
- we also sneak in updating lines children as well, by checking when ptsBuf[ptr] is NaN
*/
for (let i = 3, ptr = (pts_buffer >> 2) + 9; i < this.obj3d.children.length; i += 1, ptr += 3) {
@ -455,8 +464,9 @@ 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)
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