import { useRef, useState, useEffect, useMemo } from 'react' import { getNodePathFromSourceRange, getNodeFromPath, CallExpression, changeArguments, VariableDeclarator, } 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' import { Vector3, DoubleSide, Quaternion, Vector2 } from 'three' import { combineTransformsAlt } from '../lang/sketch' 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() as any const [hovered, setHover] = useState(false) return ( <> { setHover(true) setHighlightRange(sourceRange) }} onPointerOut={(event) => { setHover(false) setHighlightRange([0, 0]) }} > ) } 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() as any const [hovered, setHover] = useState(false) return ( <> { setHover(true) setHighlightRange(sourceRange) }} onPointerOut={(event) => { setHover(false) setHighlightRange([0, 0]) }} > ) } 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() as any const detectionPlaneRef = useRef() as any const lastPointerRef = useRef(new Vector3()) const point2DRef = useRef(new Vector2()) 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) let [x, y] = [ roundOff(point2DRef.current.x, 2), roundOff(point2DRef.current.y, 2), ] 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]) let clickDetectPlaneQuaternion = new Quaternion() let position = new Vector3(0, 0, 0) if ( guiMode.mode === 'canEditSketch' || (guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit') ) { clickDetectPlaneQuaternion = guiMode.quaternion.clone() position = new Vector3(...guiMode.position) } return ( <> { setHover(true) setHighlightRange(sourceRange) }} onPointerOut={(event) => { setHover(false) setHighlightRange([0, 0]) }} onPointerDown={() => setIsMouseDown(true)} > {isMouseDown && ( { const point = a.point const transformedPoint = point.clone() const inverseQuaternion = new Quaternion() if ( guiMode.mode === 'canEditSketch' || (guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit') ) { inverseQuaternion.copy(guiMode.quaternion.clone().invert()) } transformedPoint.applyQuaternion(inverseQuaternion) transformedPoint.sub( position.clone().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.clone().applyQuaternion(inverseQuaternion), lastPointerRef.current .clone() .applyQuaternion(inverseQuaternion) ) if (originalXY[0] === -1) { // x arg is not a literal and should be locked diff.x = 0 } if (originalXY[1] === -1) { // y arg is not a literal and should be locked diff.y = 0 } ref.current.position.add( diff.applyQuaternion(inverseQuaternion.invert()) ) lastPointerRef.current.set(point.x, point.y, point.z) } }} > )} ) } export function RenderViewerArtifacts({ artifact, forceHighlight = false, }: { artifact: ViewerArtifact forceHighlight?: boolean }) { const { selectionRange, guiMode, ast, setGuiMode, programMemory } = useStore( ({ selectionRange, guiMode, ast, setGuiMode, programMemory }) => ({ selectionRange, guiMode, ast, setGuiMode, programMemory, }) ) const [editorCursor, setEditorCursor] = useState(false) useEffect(() => { const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange) setEditorCursor(shouldHighlight && artifact.type !== 'sketch') }, [selectionRange, artifact.sourceRange]) 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 varDec: VariableDeclarator = getNodeFromPath( ast, pathToNode, 'VariableDeclarator' ) const varName = varDec?.id?.name const { quaternion, position } = combineTransformsAlt( programMemory.root[varName] ) setGuiMode({ mode: 'canEditSketch', pathToNode, quaternion, position }) } 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 ( ) } if (artifact.type === 'sketchBase') { console.log('BASE TODO') return null } if (artifact.type === 'extrudeWall') { return ( ) } return ( <> {artifact.children.map((artifact, index) => ( ))} ) }