add start of extrude
This commit is contained in:
@ -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) => (
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -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]) {
|
||||
|
Reference in New Issue
Block a user