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 x = Math.pow(10, places)
return Math.round(num * x) / x
@ -187,7 +232,6 @@ function MovingSphere({
point,
lastPointerRef.current
)
console.log(originalXY)
if (originalXY[0] === -1) {
diff.x = 0
}
@ -279,6 +323,15 @@ export function RenderViewerArtifacts({
console.log('BASE TODO')
return null
}
if (artifact.type === 'extrudeWall') {
return (
<ExtrudeWall
geo={artifact.geo}
sourceRange={artifact.sourceRange}
forceHighlight={forceHighlight || editorCursor}
/>
)
}
return (
<>
{artifact.children.map((artifact, index) => (

View File

@ -1,5 +1,9 @@
import { BoxGeometry, SphereGeometry, BufferGeometry } from 'three'
import { mergeBufferGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils'
import {
BoxGeometry,
SphereGeometry,
BufferGeometry,
PlaneGeometry,
} from 'three'
export function baseGeo({ from }: { from: [number, number, number] }) {
const baseSphere = new SphereGeometry(0.25)
@ -7,6 +11,39 @@ export function baseGeo({ from }: { from: [number, number, number] }) {
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 {
line: BufferGeometry
tip: BufferGeometry
@ -20,26 +57,18 @@ export function lineGeo({
from: [number, number, number]
to: [number, number, number]
}): LineGeos {
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 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)
const {
centre,
Hypotenuse: Hypotenuse3d,
ry,
rz,
// sign,
} = trigCalcs({ from, to })
// 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)
lineBody.rotateY(ang1)
lineBody.rotateZ(ang2)
lineBody.rotateY(ry)
lineBody.rotateZ(rz)
lineBody.translate(centre[0], centre[1], centre[2])
// 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);
// lineEnd2.translate(from[0], from[1], from[2])
// group all three geometries
// return mergeBufferGeometries([lineBody, lineEnd1])
// return mergeBufferGeometries([lineBody, lineEnd1, lineEnd2]);
return {
line: lineBody,
tip: lineEnd1,
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],
},
])
expect(root.mySketch.sketch[0]).toEqual(root.mySketch.sketch[4].firstPath)
// hmm not sure what handle the "show" function
// expect(root.mySketch.sketch[0]).toEqual(root.mySketch.sketch[4].firstPath)
expect(_return).toEqual([
{
type: 'Identifier',
@ -195,11 +194,14 @@ function exe(
}
function removeGeoFromSketch(sketch: Transform | SketchGeo): any {
if (sketch.type !== 'sketchGeo') {
return removeGeoFromSketch(sketch.sketch)
if (sketch.type !== 'sketchGeo' && sketch.type === 'transform') {
return removeGeoFromSketch(sketch.sketch as any) // TODO fix type
}
if (sketch.type === 'sketchGeo') {
return {
...sketch,
sketch: sketch.sketch.map(({ geo, previousPath, ...rest }: any) => rest),
}
}
throw new Error('not a sketch')
}

View File

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

View File

@ -1,5 +1,5 @@
import { ProgramMemory } from './executor'
import { lineGeo, baseGeo, LineGeos } from './engine'
import { lineGeo, baseGeo, LineGeos, extrudeGeo } from './engine'
import { BufferGeometry } from 'three'
import { Quaternion, Vector3 } from 'three'
@ -21,7 +21,6 @@ export type Path =
type: 'horizontalLineTo'
name?: string
x: number
previousPath: Path
geo: BufferGeometry
sourceRange: SourceRange
}
@ -29,7 +28,6 @@ export type Path =
type: 'verticalLineTo'
name?: string
y: number
previousPath: Path
geo: BufferGeometry
sourceRange: SourceRange
}
@ -37,15 +35,12 @@ export type Path =
type: 'toPoint'
name?: string
to: Coords2d
previousPath: Path
geo: LineGeos
sourceRange: SourceRange
}
| {
type: 'close'
name?: string
firstPath: Path
previousPath: Path
geo: LineGeos
sourceRange: SourceRange
}
@ -60,7 +55,7 @@ export interface Transform {
type: 'transform'
rotation: Rotation3
transform: Translate3
sketch: SketchGeo | Transform
sketch: SketchGeo | ExtrudeGeo | Transform
sourceRange: SourceRange
}
@ -70,6 +65,20 @@ export interface SketchGeo {
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) {
const geo = baseGeo({ from: [0, 0, 0] })
const base: Path = {
@ -159,8 +168,6 @@ export const sketchFns = {
const newPath: Path = {
type: 'close',
firstPath,
previousPath: lastPath,
geo: lineGeo({ from: [...from, 0], to: [...to, 0] }),
sourceRange,
}
@ -195,7 +202,6 @@ export const sketchFns = {
const currentPath: Path = {
type: 'toPoint',
to: [x, y],
previousPath: lastPath,
geo: lineGeo({ from: [...from, 0], to: [x, y, 0] }),
sourceRange,
}
@ -213,6 +219,101 @@ export const sketchFns = {
rx: RotateOnAxis([1, 0, 0]),
ry: RotateOnAxis([0, 1, 0]),
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]) {