add start of extrude

This commit is contained in:
Kurt Hutten IrevDev
2022-12-30 14:09:07 +11:00
parent 8818d9cec1
commit f6c4250947
5 changed files with 327 additions and 58 deletions

View File

@ -61,6 +61,51 @@ function SketchLine({
) )
} }
function ExtrudeWall({
geo,
sourceRange,
forceHighlight = false,
}: {
geo: BufferGeometry
sourceRange: [number, number]
forceHighlight?: boolean
}) {
const { setHighlightRange } = useStore(
({ setHighlightRange, selectionRange, guiMode, setGuiMode, ast }) => ({
setHighlightRange,
selectionRange,
guiMode,
setGuiMode,
ast,
})
)
// This reference will give us direct access to the mesh
const ref = useRef<BufferGeometry | undefined>() as any
const [hovered, setHover] = useState(false)
return (
<>
<mesh
ref={ref}
onPointerOver={(event) => {
setHover(true)
setHighlightRange(sourceRange)
}}
onPointerOut={(event) => {
setHover(false)
setHighlightRange([0, 0])
}}
>
<primitive object={geo} />
<meshStandardMaterial
side={DoubleSide}
color={hovered ? 'hotpink' : forceHighlight ? 'skyblue' : 'orange'}
/>
</mesh>
</>
)
}
const roundOff = (num: number, places: number): number => { const roundOff = (num: number, places: number): number => {
const x = Math.pow(10, places) const x = Math.pow(10, places)
return Math.round(num * x) / x return Math.round(num * x) / x
@ -187,7 +232,6 @@ function MovingSphere({
point, point,
lastPointerRef.current lastPointerRef.current
) )
console.log(originalXY)
if (originalXY[0] === -1) { if (originalXY[0] === -1) {
diff.x = 0 diff.x = 0
} }
@ -279,6 +323,15 @@ export function RenderViewerArtifacts({
console.log('BASE TODO') console.log('BASE TODO')
return null return null
} }
if (artifact.type === 'extrudeWall') {
return (
<ExtrudeWall
geo={artifact.geo}
sourceRange={artifact.sourceRange}
forceHighlight={forceHighlight || editorCursor}
/>
)
}
return ( return (
<> <>
{artifact.children.map((artifact, index) => ( {artifact.children.map((artifact, index) => (

View File

@ -1,5 +1,9 @@
import { BoxGeometry, SphereGeometry, BufferGeometry } from 'three' import {
import { mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils' BoxGeometry,
SphereGeometry,
BufferGeometry,
PlaneGeometry,
} from 'three'
export function baseGeo({ from }: { from: [number, number, number] }) { export function baseGeo({ from }: { from: [number, number, number] }) {
const baseSphere = new SphereGeometry(0.25) const baseSphere = new SphereGeometry(0.25)
@ -7,6 +11,39 @@ export function baseGeo({ from }: { from: [number, number, number] }) {
return baseSphere return baseSphere
} }
function trigCalcs({
from,
to,
}: {
from: [number, number, number]
to: [number, number, number]
}) {
const sq = (a: number): number => a * a
const centre = [
(from[0] + to[0]) / 2,
(from[1] + to[1]) / 2,
(from[2] + to[2]) / 2,
]
const Hypotenuse3d = Math.sqrt(
sq(from[0] - to[0]) + sq(from[1] - to[1]) + sq(from[2] - to[2])
)
const ry = Math.atan2(from[2] - to[2], from[0] - to[0])
const Hypotenuse2d = Math.sqrt(sq(from[0] - to[0]) + sq(from[2] - to[2]))
const rz =
Math.abs(Math.atan((to[1] - from[1]) / Hypotenuse2d)) *
Math.sign(to[1] - from[1]) *
(Math.sign(to[0] - from[0]) || 1)
const sign = ry === 0 ? 1 : -1
return {
centre,
Hypotenuse: Hypotenuse3d,
ry,
rz,
sign,
}
}
export interface LineGeos { export interface LineGeos {
line: BufferGeometry line: BufferGeometry
tip: BufferGeometry tip: BufferGeometry
@ -20,26 +57,18 @@ export function lineGeo({
from: [number, number, number] from: [number, number, number]
to: [number, number, number] to: [number, number, number]
}): LineGeos { }): LineGeos {
const sq = (a: number): number => a * a const {
const centre = [ centre,
(from[0] + to[0]) / 2, Hypotenuse: Hypotenuse3d,
(from[1] + to[1]) / 2, ry,
(from[2] + to[2]) / 2, rz,
] // sign,
const Hypotenuse3d = Math.sqrt( } = trigCalcs({ from, to })
sq(from[0] - to[0]) + sq(from[1] - to[1]) + sq(from[2] - to[2])
)
const ang1 = Math.atan2(from[2] - to[2], from[0] - to[0])
const Hypotenuse2d = Math.sqrt(sq(from[0] - to[0]) + sq(from[2] - to[2]))
const ang2 =
Math.abs(Math.atan((to[1] - from[1]) / Hypotenuse2d)) *
Math.sign(to[1] - from[1]) *
(Math.sign(to[0] - from[0]) || 1)
// create BoxGeometry with size [Hypotenuse3d, 0.1, 0.1] centered at center, with rotation of [0, ang1, ang2] // create BoxGeometry with size [Hypotenuse3d, 0.1, 0.1] centered at center, with rotation of [0, ry, rz]
const lineBody = new BoxGeometry(Hypotenuse3d, 0.1, 0.1) const lineBody = new BoxGeometry(Hypotenuse3d, 0.1, 0.1)
lineBody.rotateY(ang1) lineBody.rotateY(ry)
lineBody.rotateZ(ang2) lineBody.rotateZ(rz)
lineBody.translate(centre[0], centre[1], centre[2]) lineBody.translate(centre[0], centre[1], centre[2])
// create line end balls with SphereGeometry at `to` and `from` with radius of 0.15 // create line end balls with SphereGeometry at `to` and `from` with radius of 0.15
@ -51,12 +80,41 @@ export function lineGeo({
// const lineEnd2 = new SphereGeometry(0.15); // const lineEnd2 = new SphereGeometry(0.15);
// lineEnd2.translate(from[0], from[1], from[2]) // lineEnd2.translate(from[0], from[1], from[2])
// group all three geometries
// return mergeBufferGeometries([lineBody, lineEnd1])
// return mergeBufferGeometries([lineBody, lineEnd1, lineEnd2]);
return { return {
line: lineBody, line: lineBody,
tip: lineEnd1, tip: lineEnd1,
centre: centreSphere, centre: centreSphere,
} }
} }
export interface extrudeWallGeo {
line: BufferGeometry
tip: BufferGeometry
centre: BufferGeometry
}
export function extrudeGeo({
from,
to,
}: {
from: [number, number, number]
to: [number, number, number]
}): BufferGeometry {
const {
// centre,
Hypotenuse: Hypotenuse3d,
ry,
rz,
sign,
} = trigCalcs({ from, to })
const face = new PlaneGeometry(Hypotenuse3d, 4, 2, 2)
face.rotateX(Math.PI / 2)
face.translate(Hypotenuse3d / 2, 0, -2 * sign)
face.rotateY(ry)
face.rotateZ(rz)
face.translate(to[0], to[1], to[2])
return face
}

View File

@ -77,8 +77,7 @@ show(mySketch)
sourceRange: [93, 100], sourceRange: [93, 100],
}, },
]) ])
expect(root.mySketch.sketch[0]).toEqual(root.mySketch.sketch[4].firstPath) // expect(root.mySketch.sketch[0]).toEqual(root.mySketch.sketch[4].firstPath)
// hmm not sure what handle the "show" function
expect(_return).toEqual([ expect(_return).toEqual([
{ {
type: 'Identifier', type: 'Identifier',
@ -195,11 +194,14 @@ function exe(
} }
function removeGeoFromSketch(sketch: Transform | SketchGeo): any { function removeGeoFromSketch(sketch: Transform | SketchGeo): any {
if (sketch.type !== 'sketchGeo') { if (sketch.type !== 'sketchGeo' && sketch.type === 'transform') {
return removeGeoFromSketch(sketch.sketch) return removeGeoFromSketch(sketch.sketch as any) // TODO fix type
} }
return { if (sketch.type === 'sketchGeo') {
...sketch, return {
sketch: sketch.sketch.map(({ geo, previousPath, ...rest }: any) => rest), ...sketch,
sketch: sketch.sketch.map(({ geo, previousPath, ...rest }: any) => rest),
}
} }
throw new Error('not a sketch')
} }

View File

@ -4,7 +4,7 @@ import {
BinaryExpression, BinaryExpression,
PipeExpression, PipeExpression,
} from './abstractSyntaxTree' } from './abstractSyntaxTree'
import { Path, Transform, SketchGeo, sketchFns } from './sketch' import { Path, Transform, SketchGeo, sketchFns, ExtrudeGeo } from './sketch'
import { BufferGeometry, Quaternion } from 'three' import { BufferGeometry, Quaternion } from 'three'
import { LineGeos } from './engine' import { LineGeos } from './engine'
@ -95,7 +95,7 @@ export const executor = (
return executor(fnInit.body, fnMemory, { bodyType: 'block' }).return return executor(fnInit.body, fnMemory, { bodyType: 'block' }).return
} }
} else if (declaration.init.type === 'CallExpression') { } else if (declaration.init.type === 'CallExpression') {
const fnName = declaration.init.callee.name const functionName = declaration.init.callee.name
const fnArgs = declaration.init.arguments.map((arg) => { const fnArgs = declaration.init.arguments.map((arg) => {
if (arg.type === 'Literal') { if (arg.type === 'Literal') {
return arg.value return arg.value
@ -103,13 +103,17 @@ export const executor = (
return _programMemory.root[arg.name] return _programMemory.root[arg.name]
} }
}) })
if ('lineTo' === fnName || 'close' === fnName || 'base' === fnName) { if (
'lineTo' === functionName ||
'close' === functionName ||
'base' === functionName
) {
if (options.bodyType !== 'sketch') { if (options.bodyType !== 'sketch') {
throw new Error( throw new Error(
`Cannot call ${fnName} outside of a sketch declaration` `Cannot call ${functionName} outside of a sketch declaration`
) )
} }
const result = sketchFns[fnName]( const result = sketchFns[functionName](
_programMemory, _programMemory,
variableName, variableName,
[declaration.start, declaration.end], [declaration.start, declaration.end],
@ -117,22 +121,39 @@ export const executor = (
) )
_programMemory._sketch = result.programMemory._sketch _programMemory._sketch = result.programMemory._sketch
_programMemory.root[variableName] = result.currentPath _programMemory.root[variableName] = result.currentPath
} else if ('rx' === fnName || 'ry' === fnName || 'rz' === fnName) { } else if (
'rx' === functionName ||
'ry' === functionName ||
'rz' === functionName
) {
const sketch = declaration.init.arguments[1] const sketch = declaration.init.arguments[1]
if (sketch.type !== 'Identifier') if (sketch.type !== 'Identifier')
throw new Error('rx must be called with an identifier') throw new Error('rx must be called with an identifier')
const sketchVal = _programMemory.root[sketch.name] const sketchVal = _programMemory.root[sketch.name]
const result = sketchFns[fnName]( const result = sketchFns[functionName](
_programMemory, _programMemory,
[declaration.start, declaration.end], [declaration.start, declaration.end],
fnArgs[0], fnArgs[0],
sketchVal sketchVal
) )
_programMemory.root[variableName] = result _programMemory.root[variableName] = result
} else { } else if (functionName === 'extrude') {
_programMemory.root[variableName] = _programMemory.root[fnName]( const sketch = declaration.init.arguments[1]
...fnArgs if (sketch.type !== 'Identifier')
throw new Error('extrude must be called with an identifier')
const sketchVal = _programMemory.root[sketch.name]
const result = sketchFns[functionName](
_programMemory,
'yo',
[declaration.start, declaration.end],
fnArgs[0],
sketchVal
) )
_programMemory.root[variableName] = result
} else {
_programMemory.root[variableName] = _programMemory.root[
functionName
](...fnArgs)
} }
} }
}) })
@ -227,7 +248,7 @@ function executePipeBody(
result, result,
]) ])
} else if (expression.type === 'CallExpression') { } else if (expression.type === 'CallExpression') {
const fnName = expression.callee.name const functionName = expression.callee.name
const fnArgs = expression.arguments.map((arg) => { const fnArgs = expression.arguments.map((arg) => {
if (arg.type === 'Literal') { if (arg.type === 'Literal') {
return arg.value return arg.value
@ -238,8 +259,12 @@ function executePipeBody(
} }
throw new Error('Invalid argument type') throw new Error('Invalid argument type')
}) })
if ('rx' === fnName || 'ry' === fnName || 'rz' === fnName) { if (
const result = sketchFns[fnName]( 'rx' === functionName ||
'ry' === functionName ||
'rz' === functionName
) {
const result = sketchFns[functionName](
programMemory, programMemory,
[expression.start, expression.end], [expression.start, expression.end],
fnArgs[0], fnArgs[0],
@ -250,7 +275,20 @@ function executePipeBody(
result, result,
]) ])
} }
const result = programMemory.root[fnName](...fnArgs) if (functionName === 'extrude') {
const result = sketchFns[functionName](
programMemory,
'yo',
[expression.start, expression.end],
fnArgs[0],
fnArgs[1]
)
return executePipeBody(body, programMemory, expressionIndex + 1, [
...previousResults,
result,
])
}
const result = programMemory.root[functionName](...fnArgs)
return executePipeBody(body, programMemory, expressionIndex + 1, [ return executePipeBody(body, programMemory, expressionIndex + 1, [
...previousResults, ...previousResults,
result, result,
@ -304,6 +342,11 @@ export type ViewerArtifact =
sourceRange: SourceRange sourceRange: SourceRange
geo: BufferGeometry geo: BufferGeometry
} }
| {
type: 'extrudeWall'
sourceRange: SourceRange
geo: BufferGeometry
}
| { | {
type: 'parent' type: 'parent'
sourceRange: SourceRange sourceRange: SourceRange
@ -322,7 +365,7 @@ type PreviousTransforms = {
export const processShownObjects = ( export const processShownObjects = (
programMemory: ProgramMemory, programMemory: ProgramMemory,
geoMeta: SketchGeo | Transform, geoMeta: SketchGeo | ExtrudeGeo | Transform,
previousTransforms: PreviousTransforms = [] previousTransforms: PreviousTransforms = []
): ViewerArtifact[] => { ): ViewerArtifact[] => {
if (geoMeta?.type === 'sketchGeo') { if (geoMeta?.type === 'sketchGeo') {
@ -349,7 +392,7 @@ export const processShownObjects = (
sourceRange, sourceRange,
} }
} else if (type === 'base') { } else if (type === 'base') {
const newGeo = geo.clone() const newGeo: BufferGeometry = geo.clone()
previousTransforms.forEach(({ rotation, transform }) => { previousTransforms.forEach(({ rotation, transform }) => {
newGeo.applyQuaternion(rotation) newGeo.applyQuaternion(rotation)
newGeo.translate(transform[0], transform[1], transform[2]) newGeo.translate(transform[0], transform[1], transform[2])
@ -378,7 +421,19 @@ export const processShownObjects = (
]), ]),
} }
return [parentArtifact] return [parentArtifact]
} } else if (geoMeta.type === 'extrudeGeo') {
const result: ViewerArtifact[] = geoMeta.surfaces.map((a) => {
const geo: BufferGeometry = a.geo.clone()
geo.applyQuaternion(a.quaternion)
geo.translate(a.translate[0], a.translate[1], a.translate[2])
return {
type: 'extrudeWall',
sourceRange: a.sourceRanges[0],
geo,
}
})
return result
}
throw new Error('Unknown geoMeta type') throw new Error('Unknown geoMeta type')
} }

View File

@ -1,5 +1,5 @@
import { ProgramMemory } from './executor' import { ProgramMemory } from './executor'
import { lineGeo, baseGeo, LineGeos } from './engine' import { lineGeo, baseGeo, LineGeos, extrudeGeo } from './engine'
import { BufferGeometry } from 'three' import { BufferGeometry } from 'three'
import { Quaternion, Vector3 } from 'three' import { Quaternion, Vector3 } from 'three'
@ -21,7 +21,6 @@ export type Path =
type: 'horizontalLineTo' type: 'horizontalLineTo'
name?: string name?: string
x: number x: number
previousPath: Path
geo: BufferGeometry geo: BufferGeometry
sourceRange: SourceRange sourceRange: SourceRange
} }
@ -29,7 +28,6 @@ export type Path =
type: 'verticalLineTo' type: 'verticalLineTo'
name?: string name?: string
y: number y: number
previousPath: Path
geo: BufferGeometry geo: BufferGeometry
sourceRange: SourceRange sourceRange: SourceRange
} }
@ -37,15 +35,12 @@ export type Path =
type: 'toPoint' type: 'toPoint'
name?: string name?: string
to: Coords2d to: Coords2d
previousPath: Path
geo: LineGeos geo: LineGeos
sourceRange: SourceRange sourceRange: SourceRange
} }
| { | {
type: 'close' type: 'close'
name?: string name?: string
firstPath: Path
previousPath: Path
geo: LineGeos geo: LineGeos
sourceRange: SourceRange sourceRange: SourceRange
} }
@ -60,7 +55,7 @@ export interface Transform {
type: 'transform' type: 'transform'
rotation: Rotation3 rotation: Rotation3
transform: Translate3 transform: Translate3
sketch: SketchGeo | Transform sketch: SketchGeo | ExtrudeGeo | Transform
sourceRange: SourceRange sourceRange: SourceRange
} }
@ -70,6 +65,20 @@ export interface SketchGeo {
sourceRange: SourceRange sourceRange: SourceRange
} }
export interface ExtrudeFace {
type: 'extrudeFace'
quaternion: Quaternion
translate: [number, number, number]
geo: BufferGeometry
sourceRanges: SourceRange[]
}
export interface ExtrudeGeo {
type: 'extrudeGeo'
surfaces: ExtrudeFace[]
sourceRange: SourceRange
}
function addBasePath(programMemory: ProgramMemory) { function addBasePath(programMemory: ProgramMemory) {
const geo = baseGeo({ from: [0, 0, 0] }) const geo = baseGeo({ from: [0, 0, 0] })
const base: Path = { const base: Path = {
@ -159,8 +168,6 @@ export const sketchFns = {
const newPath: Path = { const newPath: Path = {
type: 'close', type: 'close',
firstPath,
previousPath: lastPath,
geo: lineGeo({ from: [...from, 0], to: [...to, 0] }), geo: lineGeo({ from: [...from, 0], to: [...to, 0] }),
sourceRange, sourceRange,
} }
@ -195,7 +202,6 @@ export const sketchFns = {
const currentPath: Path = { const currentPath: Path = {
type: 'toPoint', type: 'toPoint',
to: [x, y], to: [x, y],
previousPath: lastPath,
geo: lineGeo({ from: [...from, 0], to: [x, y, 0] }), geo: lineGeo({ from: [...from, 0], to: [x, y, 0] }),
sourceRange, sourceRange,
} }
@ -213,6 +219,101 @@ export const sketchFns = {
rx: RotateOnAxis([1, 0, 0]), rx: RotateOnAxis([1, 0, 0]),
ry: RotateOnAxis([0, 1, 0]), ry: RotateOnAxis([0, 1, 0]),
rz: RotateOnAxis([0, 0, 1]), rz: RotateOnAxis([0, 0, 1]),
extrude: (
programMemory: ProgramMemory,
name: string = '',
sourceRange: SourceRange,
length: any,
// sketchVal: any
sketchVal: SketchGeo | Transform
): ExtrudeGeo | Transform => {
const getSketchGeo = (sketchVal: SketchGeo | Transform): SketchGeo => {
if (
sketchVal.type === 'transform' &&
sketchVal.sketch.type === 'extrudeGeo'
)
throw new Error('Cannot extrude a extrude')
return sketchVal.type === 'transform'
? getSketchGeo(sketchVal.sketch as any) // TODO fix types
: (sketchVal as SketchGeo) // TODO fix types
}
interface CombinedTransforms {
position: [number, number, number]
quaternion: Quaternion
}
const combineATransforms = (
translate: [number, number, number],
rotate: Quaternion,
currentPosition: [number, number, number],
currentRotation: Quaternion
): CombinedTransforms => {
const newPosition = new Vector3(...currentPosition).applyQuaternion(
rotate
)
newPosition.add(new Vector3(...translate))
const newQuaternion = new Quaternion().multiplyQuaternions(
rotate.clone(),
currentRotation
)
return {
position: [newPosition.x, newPosition.y, newPosition.z],
quaternion: newQuaternion,
}
}
const transformFromSketch = (
sketchVal: SketchGeo | ExtrudeGeo | Transform,
currentPosition: [number, number, number] = [0, 0, 0],
currentRotation: Quaternion = new Quaternion()
): CombinedTransforms => {
if (sketchVal.type === 'transform') {
const { transform, rotation } = sketchVal
const { position, quaternion } = combineATransforms(
transform,
rotation,
currentPosition,
currentRotation
)
return transformFromSketch(sketchVal.sketch, position, quaternion)
}
return {
position: currentPosition,
quaternion: currentRotation,
}
}
const sketch = getSketchGeo(sketchVal)
const { position, quaternion } = transformFromSketch(sketchVal)
const extrudeFaces: ExtrudeFace[] = []
console.log('sketch', sketch)
sketch.sketch.map((line, index) => {
if (line.type === 'toPoint' && index !== 0) {
const lastPoint = sketch.sketch[index - 1]
let from: [number, number] = [0, 0]
if (lastPoint.type === 'toPoint') {
from = lastPoint.to
} else if (lastPoint.type === 'base') {
from = lastPoint.from
}
const to = line.to
const geo = extrudeGeo({
from: [from[0], from[1], 0],
to: [to[0], to[1], 0],
})
extrudeFaces.push({
type: 'extrudeFace',
quaternion,
translate: position,
geo,
sourceRanges: [line.sourceRange, sourceRange],
})
}
})
return {
type: 'extrudeGeo',
sourceRange,
surfaces: extrudeFaces,
}
},
} }
function RotateOnAxis(axisMultiplier: [number, number, number]) { function RotateOnAxis(axisMultiplier: [number, number, number]) {