2022-12-07 10:02:21 +11:00
|
|
|
import { useRef, useState, useEffect, useMemo } from 'react'
|
|
|
|
import {
|
|
|
|
getNodePathFromSourceRange,
|
|
|
|
getNodeFromPath,
|
|
|
|
CallExpression,
|
|
|
|
changeArguments,
|
2023-01-04 01:28:26 +11:00
|
|
|
VariableDeclarator,
|
2022-12-07 10:02:21 +11:00
|
|
|
} from '../lang/abstractSyntaxTree'
|
|
|
|
import { ViewerArtifact } from '../lang/executor'
|
|
|
|
import { BufferGeometry } from 'three'
|
|
|
|
import { useStore } from '../useStore'
|
|
|
|
import { isOverlapping } from '../lib/utils'
|
|
|
|
import { LineGeos } from '../lang/engine'
|
2022-12-23 07:37:42 +11:00
|
|
|
import { Vector3, DoubleSide, Quaternion, Vector2 } from 'three'
|
2023-01-04 01:28:26 +11:00
|
|
|
import { combineTransformsAlt } from '../lang/sketch'
|
2023-01-06 09:07:22 +11:00
|
|
|
import { useSetCursor } from '../hooks/useSetCursor'
|
2022-12-07 10:02:21 +11:00
|
|
|
|
|
|
|
function SketchLine({
|
|
|
|
geo,
|
|
|
|
sourceRange,
|
|
|
|
forceHighlight = false,
|
|
|
|
}: {
|
|
|
|
geo: LineGeos
|
|
|
|
sourceRange: [number, number]
|
|
|
|
forceHighlight?: boolean
|
|
|
|
}) {
|
2023-01-06 09:07:22 +11:00
|
|
|
const { setHighlightRange } = useStore(({ setHighlightRange }) => ({
|
|
|
|
setHighlightRange,
|
|
|
|
}))
|
|
|
|
const onClick = useSetCursor(sourceRange)
|
2022-12-07 10:02:21 +11:00
|
|
|
// 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])
|
|
|
|
}}
|
2023-01-06 09:07:22 +11:00
|
|
|
onClick={onClick}
|
2022-12-07 10:02:21 +11:00
|
|
|
>
|
|
|
|
<primitive object={geo.line} />
|
|
|
|
<meshStandardMaterial
|
2022-12-25 21:14:43 +11:00
|
|
|
color={hovered ? 'hotpink' : forceHighlight ? 'skyblue' : 'orange'}
|
2022-12-07 10:02:21 +11:00
|
|
|
/>
|
|
|
|
</mesh>
|
|
|
|
<MovingSphere
|
|
|
|
geo={geo.tip}
|
|
|
|
sourceRange={sourceRange}
|
2022-12-25 21:14:43 +11:00
|
|
|
editorCursor={forceHighlight}
|
2022-12-07 10:02:21 +11:00
|
|
|
/>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-12-30 14:09:07 +11:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
)
|
2023-01-06 09:07:22 +11:00
|
|
|
const onClick = useSetCursor(sourceRange)
|
2022-12-30 14:09:07 +11:00
|
|
|
// 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])
|
|
|
|
}}
|
2023-01-06 09:07:22 +11:00
|
|
|
onClick={onClick}
|
2022-12-30 14:09:07 +11:00
|
|
|
>
|
|
|
|
<primitive object={geo} />
|
|
|
|
<meshStandardMaterial
|
|
|
|
side={DoubleSide}
|
|
|
|
color={hovered ? 'hotpink' : forceHighlight ? 'skyblue' : 'orange'}
|
|
|
|
/>
|
|
|
|
</mesh>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-12-07 10:02:21 +11:00
|
|
|
const roundOff = (num: number, places: number): number => {
|
|
|
|
const x = Math.pow(10, places)
|
|
|
|
return Math.round(num * x) / x
|
|
|
|
}
|
|
|
|
|
|
|
|
function MovingSphere({
|
|
|
|
geo,
|
|
|
|
sourceRange,
|
|
|
|
editorCursor,
|
|
|
|
}: {
|
|
|
|
geo: BufferGeometry
|
|
|
|
sourceRange: [number, number]
|
|
|
|
editorCursor: boolean
|
|
|
|
}) {
|
|
|
|
const ref = useRef<BufferGeometry | undefined>() as any
|
2022-12-23 07:37:42 +11:00
|
|
|
const detectionPlaneRef = useRef<BufferGeometry | undefined>() as any
|
2022-12-07 10:02:21 +11:00
|
|
|
const lastPointerRef = useRef<Vector3>(new Vector3())
|
2022-12-23 07:37:42 +11:00
|
|
|
const point2DRef = useRef<Vector2>(new Vector2())
|
2022-12-07 10:02:21 +11:00
|
|
|
const [hovered, setHover] = useState(false)
|
|
|
|
const [isMouseDown, setIsMouseDown] = useState(false)
|
|
|
|
|
|
|
|
const { setHighlightRange, guiMode, ast, updateAst } = useStore((s) => ({
|
|
|
|
setHighlightRange: s.setHighlightRange,
|
|
|
|
guiMode: s.guiMode,
|
|
|
|
ast: s.ast,
|
|
|
|
updateAst: s.updateAst,
|
|
|
|
}))
|
|
|
|
const { originalXY } = useMemo(() => {
|
|
|
|
if (ast) {
|
|
|
|
const thePath = getNodePathFromSourceRange(ast, sourceRange)
|
|
|
|
const callExpression = getNodeFromPath(ast, thePath) as CallExpression
|
|
|
|
const [xArg, yArg] = callExpression?.arguments || []
|
|
|
|
const x = xArg?.type === 'Literal' ? xArg.value : -1
|
|
|
|
const y = yArg?.type === 'Literal' ? yArg.value : -1
|
|
|
|
return {
|
|
|
|
originalXY: [x, y],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
originalXY: [-1, -1],
|
|
|
|
}
|
|
|
|
}, [ast])
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const handleMouseUp = () => {
|
|
|
|
if (isMouseDown && ast) {
|
|
|
|
const thePath = getNodePathFromSourceRange(ast, sourceRange)
|
2022-12-23 07:37:42 +11:00
|
|
|
let [x, y] = [
|
|
|
|
roundOff(point2DRef.current.x, 2),
|
|
|
|
roundOff(point2DRef.current.y, 2),
|
2022-12-07 10:02:21 +11:00
|
|
|
]
|
2022-12-23 07:37:42 +11:00
|
|
|
let theNewPoints: [number, number] = [x, y]
|
2022-12-07 10:02:21 +11:00
|
|
|
const { modifiedAst } = changeArguments(ast, thePath, theNewPoints)
|
|
|
|
updateAst(modifiedAst)
|
|
|
|
ref.current.position.set(0, 0, 0)
|
|
|
|
}
|
|
|
|
setIsMouseDown(false)
|
|
|
|
}
|
|
|
|
window.addEventListener('mouseup', handleMouseUp)
|
|
|
|
return () => {
|
|
|
|
window.removeEventListener('mouseup', handleMouseUp)
|
|
|
|
}
|
|
|
|
}, [isMouseDown, ast])
|
|
|
|
|
2022-12-23 07:37:42 +11:00
|
|
|
let clickDetectPlaneQuaternion = new Quaternion()
|
2023-01-04 01:28:26 +11:00
|
|
|
let position = new Vector3(0, 0, 0)
|
2022-12-23 07:37:42 +11:00
|
|
|
if (
|
|
|
|
guiMode.mode === 'canEditSketch' ||
|
|
|
|
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
|
|
|
|
) {
|
|
|
|
clickDetectPlaneQuaternion = guiMode.quaternion.clone()
|
2023-01-04 01:28:26 +11:00
|
|
|
position = new Vector3(...guiMode.position)
|
2022-12-23 07:37:42 +11:00
|
|
|
}
|
|
|
|
|
2022-12-07 10:02:21 +11:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<mesh
|
|
|
|
ref={ref}
|
|
|
|
onPointerOver={(event) => {
|
|
|
|
setHover(true)
|
|
|
|
setHighlightRange(sourceRange)
|
|
|
|
}}
|
|
|
|
onPointerOut={(event) => {
|
|
|
|
setHover(false)
|
|
|
|
setHighlightRange([0, 0])
|
|
|
|
}}
|
|
|
|
onPointerDown={() => setIsMouseDown(true)}
|
|
|
|
>
|
|
|
|
<primitive object={geo} scale={hovered ? 2 : 1} />
|
|
|
|
<meshStandardMaterial
|
|
|
|
color={hovered ? 'hotpink' : editorCursor ? 'skyblue' : 'orange'}
|
|
|
|
/>
|
|
|
|
</mesh>
|
|
|
|
{isMouseDown && (
|
|
|
|
<mesh
|
2023-01-04 01:28:26 +11:00
|
|
|
position={position}
|
2022-12-23 07:37:42 +11:00
|
|
|
quaternion={clickDetectPlaneQuaternion}
|
2022-12-07 10:02:21 +11:00
|
|
|
onPointerMove={(a) => {
|
|
|
|
const point = a.point
|
2022-12-23 07:37:42 +11:00
|
|
|
|
|
|
|
const transformedPoint = point.clone()
|
2023-01-05 15:01:27 +11:00
|
|
|
const inverseQuaternion = new Quaternion()
|
2022-12-23 07:37:42 +11:00
|
|
|
if (
|
|
|
|
guiMode.mode === 'canEditSketch' ||
|
|
|
|
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
|
|
|
|
) {
|
2023-01-05 15:01:27 +11:00
|
|
|
inverseQuaternion.copy(guiMode.quaternion.clone().invert())
|
2022-12-23 07:37:42 +11:00
|
|
|
}
|
|
|
|
transformedPoint.applyQuaternion(inverseQuaternion)
|
2023-01-05 15:01:27 +11:00
|
|
|
transformedPoint.sub(
|
|
|
|
position.clone().applyQuaternion(inverseQuaternion)
|
|
|
|
)
|
2022-12-23 07:37:42 +11:00
|
|
|
point2DRef.current.set(transformedPoint.x, transformedPoint.y)
|
|
|
|
|
2022-12-07 10:02:21 +11:00
|
|
|
if (
|
|
|
|
lastPointerRef.current.x === 0 &&
|
|
|
|
lastPointerRef.current.y === 0 &&
|
|
|
|
lastPointerRef.current.z === 0
|
|
|
|
) {
|
|
|
|
lastPointerRef.current.set(point.x, point.y, point.z)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if (guiMode.mode)
|
|
|
|
if (ref.current) {
|
|
|
|
const diff = new Vector3().subVectors(
|
2023-01-05 15:01:27 +11:00
|
|
|
point.clone().applyQuaternion(inverseQuaternion),
|
2022-12-07 10:02:21 +11:00
|
|
|
lastPointerRef.current
|
2023-01-05 15:01:27 +11:00
|
|
|
.clone()
|
|
|
|
.applyQuaternion(inverseQuaternion)
|
2022-12-07 10:02:21 +11:00
|
|
|
)
|
|
|
|
if (originalXY[0] === -1) {
|
2023-01-05 15:01:27 +11:00
|
|
|
// x arg is not a literal and should be locked
|
2022-12-07 10:02:21 +11:00
|
|
|
diff.x = 0
|
|
|
|
}
|
|
|
|
if (originalXY[1] === -1) {
|
2023-01-05 15:01:27 +11:00
|
|
|
// y arg is not a literal and should be locked
|
2022-12-07 10:02:21 +11:00
|
|
|
diff.y = 0
|
|
|
|
}
|
2023-01-05 15:01:27 +11:00
|
|
|
ref.current.position.add(
|
|
|
|
diff.applyQuaternion(inverseQuaternion.invert())
|
|
|
|
)
|
2022-12-07 10:02:21 +11:00
|
|
|
lastPointerRef.current.set(point.x, point.y, point.z)
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
>
|
2022-12-23 07:37:42 +11:00
|
|
|
<planeGeometry args={[50, 50]} ref={detectionPlaneRef} />
|
|
|
|
<meshStandardMaterial
|
|
|
|
side={DoubleSide}
|
|
|
|
color="blue"
|
|
|
|
transparent
|
|
|
|
opacity={0}
|
|
|
|
/>
|
2022-12-07 10:02:21 +11:00
|
|
|
</mesh>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function RenderViewerArtifacts({
|
|
|
|
artifact,
|
|
|
|
forceHighlight = false,
|
|
|
|
}: {
|
|
|
|
artifact: ViewerArtifact
|
|
|
|
forceHighlight?: boolean
|
|
|
|
}) {
|
2023-01-04 01:28:26 +11:00
|
|
|
const { selectionRange, guiMode, ast, setGuiMode, programMemory } = useStore(
|
|
|
|
({ selectionRange, guiMode, ast, setGuiMode, programMemory }) => ({
|
2022-12-25 21:14:43 +11:00
|
|
|
selectionRange,
|
|
|
|
guiMode,
|
|
|
|
ast,
|
|
|
|
setGuiMode,
|
2023-01-04 01:28:26 +11:00
|
|
|
programMemory,
|
2022-12-25 21:14:43 +11:00
|
|
|
})
|
|
|
|
)
|
2022-12-07 10:02:21 +11:00
|
|
|
const [editorCursor, setEditorCursor] = useState(false)
|
|
|
|
useEffect(() => {
|
|
|
|
const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange)
|
2022-12-25 21:14:43 +11:00
|
|
|
setEditorCursor(shouldHighlight && artifact.type !== 'sketch')
|
2022-12-07 10:02:21 +11:00
|
|
|
}, [selectionRange, artifact.sourceRange])
|
2022-12-25 21:14:43 +11:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange)
|
|
|
|
if (
|
|
|
|
shouldHighlight &&
|
|
|
|
(guiMode.mode === 'default' || guiMode.mode === 'canEditSketch') &&
|
|
|
|
artifact.type === 'sketch' &&
|
|
|
|
ast
|
|
|
|
) {
|
|
|
|
const pathToNode = getNodePathFromSourceRange(ast, artifact.sourceRange)
|
2023-01-04 01:28:26 +11:00
|
|
|
const varDec: VariableDeclarator = getNodeFromPath(
|
|
|
|
ast,
|
|
|
|
pathToNode,
|
|
|
|
'VariableDeclarator'
|
|
|
|
)
|
|
|
|
const varName = varDec?.id?.name
|
|
|
|
const { quaternion, position } = combineTransformsAlt(
|
|
|
|
programMemory.root[varName]
|
|
|
|
)
|
|
|
|
setGuiMode({ mode: 'canEditSketch', pathToNode, quaternion, position })
|
2022-12-25 21:14:43 +11:00
|
|
|
} else if (
|
|
|
|
!shouldHighlight &&
|
|
|
|
guiMode.mode === 'canEditSketch' &&
|
|
|
|
artifact.type === 'sketch'
|
|
|
|
) {
|
|
|
|
setGuiMode({ mode: 'default' })
|
|
|
|
}
|
|
|
|
}, [selectionRange, artifact.sourceRange, ast, guiMode.mode, setGuiMode])
|
2022-12-07 10:02:21 +11:00
|
|
|
if (artifact.type === 'sketchLine') {
|
|
|
|
const { geo, sourceRange } = artifact
|
|
|
|
return (
|
|
|
|
<SketchLine
|
|
|
|
geo={geo}
|
|
|
|
sourceRange={sourceRange}
|
|
|
|
forceHighlight={forceHighlight || editorCursor}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if (artifact.type === 'sketchBase') {
|
|
|
|
console.log('BASE TODO')
|
|
|
|
return null
|
|
|
|
}
|
2022-12-30 14:09:07 +11:00
|
|
|
if (artifact.type === 'extrudeWall') {
|
|
|
|
return (
|
|
|
|
<ExtrudeWall
|
|
|
|
geo={artifact.geo}
|
|
|
|
sourceRange={artifact.sourceRange}
|
|
|
|
forceHighlight={forceHighlight || editorCursor}
|
|
|
|
/>
|
|
|
|
)
|
|
|
|
}
|
2022-12-07 10:02:21 +11:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{artifact.children.map((artifact, index) => (
|
|
|
|
<RenderViewerArtifacts
|
|
|
|
artifact={artifact}
|
|
|
|
key={index}
|
|
|
|
forceHighlight={forceHighlight || editorCursor}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|