Files
modeling-app/src/components/SketchLine.tsx

294 lines
8.5 KiB
TypeScript
Raw Normal View History

import { useRef, useState, useEffect, useMemo } from 'react'
import {
getNodePathFromSourceRange,
getNodeFromPath,
CallExpression,
changeArguments,
} 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'
function SketchLine({
geo,
sourceRange,
forceHighlight = false,
}: {
geo: LineGeos
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.line} />
<meshStandardMaterial
2022-12-25 21:14:43 +11:00
color={hovered ? 'hotpink' : forceHighlight ? 'skyblue' : 'orange'}
/>
</mesh>
<MovingSphere
geo={geo.tip}
sourceRange={sourceRange}
2022-12-25 21:14:43 +11:00
editorCursor={forceHighlight}
/>
</>
)
}
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
const lastPointerRef = useRef<Vector3>(new Vector3())
2022-12-23 07:37:42 +11:00
const point2DRef = useRef<Vector2>(new Vector2())
const [hovered, setHover] = useState(false)
const [isMouseDown, setIsMouseDown] = useState(false)
const { setHighlightRange, guiMode, ast, updateAst } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
selectionRange: s.selectionRange,
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
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-23 07:37:42 +11:00
let theNewPoints: [number, number] = [x, y]
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()
if (
guiMode.mode === 'canEditSketch' ||
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
) {
clickDetectPlaneQuaternion = guiMode.quaternion.clone()
}
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
2022-12-23 07:37:42 +11:00
position={[0, 0, -0.05]}
quaternion={clickDetectPlaneQuaternion}
onPointerMove={(a) => {
const point = a.point
2022-12-23 07:37:42 +11:00
const transformedPoint = point.clone()
let inverseQuaternion = new Quaternion()
if (
guiMode.mode === 'canEditSketch' ||
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
) {
inverseQuaternion = guiMode.quaternion.clone()
}
inverseQuaternion = inverseQuaternion.invert()
transformedPoint.applyQuaternion(inverseQuaternion)
point2DRef.current.set(transformedPoint.x, transformedPoint.y)
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(
point,
lastPointerRef.current
)
console.log(originalXY)
if (originalXY[0] === -1) {
diff.x = 0
}
if (originalXY[1] === -1) {
diff.y = 0
}
ref.current.position.add(diff)
lastPointerRef.current.set(point.x, point.y, point.z)
}
}}
name="my-mesh"
>
2022-12-23 07:37:42 +11:00
<planeGeometry args={[50, 50]} ref={detectionPlaneRef} />
<meshStandardMaterial
side={DoubleSide}
color="blue"
transparent
opacity={0}
/>
</mesh>
)}
</>
)
}
export function RenderViewerArtifacts({
artifact,
forceHighlight = false,
}: {
artifact: ViewerArtifact
forceHighlight?: boolean
}) {
2022-12-25 21:14:43 +11:00
const { selectionRange, guiMode, ast, setGuiMode } = useStore(
({ selectionRange, guiMode, ast, setGuiMode }) => ({
selectionRange,
guiMode,
ast,
setGuiMode,
})
)
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')
}, [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)
const piper = getNodeFromPath(ast, pathToNode, 'PipeExpression')
const quaternion = new Quaternion()
if (piper.type === 'PipeExpression') {
const rotateName = piper?.body?.[1]?.callee?.name
const rotateValue = piper?.body?.[1]?.arguments[0].value
let rotateAxis = new Vector3(1, 0, 0)
if (rotateName === 'ry') {
rotateAxis = new Vector3(0, 1, 0)
} else if (rotateName === 'rz') {
rotateAxis = new Vector3(0, 0, 1)
}
quaternion.setFromAxisAngle(rotateAxis, (Math.PI * rotateValue) / 180)
}
setGuiMode({ mode: 'canEditSketch', pathToNode, quaternion })
} else if (
!shouldHighlight &&
guiMode.mode === 'canEditSketch' &&
artifact.type === 'sketch'
) {
setGuiMode({ mode: 'default' })
}
}, [selectionRange, artifact.sourceRange, ast, guiMode.mode, setGuiMode])
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
}
return (
<>
{artifact.children.map((artifact, index) => (
<RenderViewerArtifacts
artifact={artifact}
key={index}
forceHighlight={forceHighlight || editorCursor}
/>
))}
</>
)
}