import { useRef, useState, useEffect, useMemo } from 'react' import { CallExpression, ArrayExpression } from '../lang/abstractSyntaxTree' import { getNodePathFromSourceRange, getNodeFromPath } from '../lang/queryAst' import { changeSketchArguments } from '../lang/std/sketch' import { ExtrudeGroup, ExtrudeSurface, SketchGroup, Path, Rotation, Position, PathToNode, SourceRange, } from '../lang/executor' import { BufferGeometry } from 'three' import { useStore } from '../useStore' import { isOverlap, roundOff } from '../lib/utils' import { Vector3, DoubleSide, Quaternion } from 'three' import { useSetCursor } from '../hooks/useSetCursor' function MovingSphere({ geo, sourceRange, editorCursor, rotation, position, from, }: { geo: BufferGeometry sourceRange: [number, number] editorCursor: boolean rotation: Rotation position: Position from: [number, number] }) { const ref = useRef() as any const detectionPlaneRef = useRef() as any const lastPointerRef = useRef(new Vector3()) const point2DRef = useRef(new Vector3()) const [hovered, setHover] = useState(false) const [isMouseDown, setIsMouseDown] = useState(false) const { setHighlightRange, guiMode, ast, updateAst, programMemory } = useStore((s) => ({ setHighlightRange: s.setHighlightRange, guiMode: s.guiMode, ast: s.ast, updateAst: s.updateAst, programMemory: s.programMemory, })) const { originalXY } = useMemo(() => { if (ast) { const thePath = getNodePathFromSourceRange(ast, sourceRange) const { node: callExpression } = getNodeFromPath( ast, thePath ) const [xArg, yArg] = guiMode.mode === 'sketch' ? callExpression?.arguments || [] : (callExpression?.arguments?.[0] as ArrayExpression)?.elements || [] 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) const current2d = point2DRef.current.clone() const inverseQuaternion = new Quaternion() if ( guiMode.mode === 'canEditSketch' || (guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit') ) { inverseQuaternion.set(...guiMode.rotation) inverseQuaternion.invert() } current2d.sub( new Vector3(...position).applyQuaternion(inverseQuaternion) ) let [x, y] = [roundOff(current2d.x, 2), roundOff(current2d.y, 2)] let theNewPoints: [number, number] = [x, y] const { modifiedAst } = changeSketchArguments( ast, programMemory, sourceRange, theNewPoints, guiMode, from ) updateAst(modifiedAst) ref.current.position.set(...position) } setIsMouseDown(false) } window.addEventListener('mouseup', handleMouseUp) return () => { window.removeEventListener('mouseup', handleMouseUp) } }, [isMouseDown]) const inEditMode = guiMode.mode === 'canEditSketch' || (guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit') let clickDetectPlaneQuaternion = new Quaternion() if (inEditMode) { clickDetectPlaneQuaternion = new Quaternion(...rotation) } return ( <> { inEditMode && setHover(true) setHighlightRange(sourceRange) }} onPointerOut={(event) => { setHover(false) setHighlightRange([0, 0]) }} onPointerDown={() => inEditMode && 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.set(...guiMode.rotation) inverseQuaternion.invert() } transformedPoint.applyQuaternion(inverseQuaternion) point2DRef.current.copy(transformedPoint) 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.copy(point.clone()) } }} > )} ) } export function RenderViewerArtifacts({ artifacts, }: { artifacts: (ExtrudeGroup | SketchGroup)[] }) { useSetAppModeFromCursorLocation(artifacts) return ( <> {artifacts?.map((artifact, i) => ( ))} ) } function RenderViewerArtifact({ artifact, }: { artifact: ExtrudeGroup | SketchGroup }) { // const { selectionRange, guiMode, ast, setGuiMode } = useStore( // ({ selectionRange, guiMode, ast, setGuiMode }) => ({ // selectionRange, // guiMode, // ast, // setGuiMode, // }) // ) // const [editorCursor, setEditorCursor] = useState(false) // useEffect(() => { // const shouldHighlight = isOverlapping( // artifact.__meta.slice(-1)[0].sourceRange, // selectionRange // ) // setEditorCursor(shouldHighlight) // }, [selectionRange, artifact.__meta]) if (artifact.type === 'sketchGroup') { return ( <> {artifact.value.map((geoInfo, key) => ( ))} ) } if (artifact.type === 'extrudeGroup') { return ( <> {artifact.value.map((geoInfo, key) => ( ))} ) } return null } function WallRender({ geoInfo, forceHighlight = false, rotation, position, }: { geoInfo: ExtrudeSurface forceHighlight?: boolean rotation: Rotation position: Position }) { const { setHighlightRange, selectionRanges } = useStore( ({ setHighlightRange, selectionRanges }) => ({ setHighlightRange, selectionRanges, }) ) const onClick = useSetCursor(geoInfo.__geoMeta.sourceRange) // This reference will give us direct access to the mesh const ref = useRef() as any const [hovered, setHover] = useState(false) const [editorCursor, setEditorCursor] = useState(false) useEffect(() => { const shouldHighlight = selectionRanges.some((range) => isOverlap(geoInfo.__geoMeta.sourceRange, range) ) setEditorCursor(shouldHighlight) }, [selectionRanges, geoInfo]) return ( <> { setHover(true) setHighlightRange(geoInfo.__geoMeta.sourceRange) }} onPointerOut={(event) => { setHover(false) setHighlightRange([0, 0]) }} onClick={onClick} > ) } function PathRender({ geoInfo, forceHighlight = false, rotation, position, }: { geoInfo: Path forceHighlight?: boolean rotation: Rotation position: Position }) { const { selectionRanges } = useStore(({ selectionRanges }) => ({ selectionRanges, })) const [editorCursor, setEditorCursor] = useState(false) useEffect(() => { const shouldHighlight = selectionRanges.some((range) => isOverlap(geoInfo.__geoMeta.sourceRange, range) ) setEditorCursor(shouldHighlight) }, [selectionRanges, geoInfo]) return ( <> {geoInfo.__geoMeta.geos.map((meta, i) => { if (meta.type === 'line') { return ( ) } if (meta.type === 'lineEnd') { return ( ) } })} ) } function LineRender({ geo, sourceRange, forceHighlight = false, rotation, position, }: { geo: BufferGeometry sourceRange: [number, number] forceHighlight?: boolean rotation: Rotation position: Position }) { const { setHighlightRange } = useStore((s) => ({ setHighlightRange: s.setHighlightRange, })) const onClick = useSetCursor(sourceRange) // 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]) }} onClick={onClick} > ) } type Artifact = ExtrudeGroup | SketchGroup function useSetAppModeFromCursorLocation(artifacts: Artifact[]) { const { selectionRanges, guiMode, setGuiMode, ast } = useStore( ({ selectionRanges, guiMode, setGuiMode, ast }) => ({ selectionRanges, guiMode, setGuiMode, ast, }) ) useEffect(() => { const artifactsWithinCursorRange: ( | { parentType: Artifact['type'] isParent: true pathToNode: PathToNode sourceRange: SourceRange rotation: Rotation position: Position } | { parentType: Artifact['type'] isParent: false pathToNode: PathToNode sourceRange: SourceRange rotation: Rotation position: Position } )[] = [] artifacts?.forEach((artifact) => { artifact.value.forEach((geo) => { if (isOverlap(geo.__geoMeta.sourceRange, selectionRanges[0])) { artifactsWithinCursorRange.push({ parentType: artifact.type, isParent: false, pathToNode: geo.__geoMeta.pathToNode, sourceRange: geo.__geoMeta.sourceRange, rotation: artifact.rotation, position: artifact.position, }) } }) artifact.__meta.forEach((meta) => { if (isOverlap(meta.sourceRange, selectionRanges[0])) { artifactsWithinCursorRange.push({ parentType: artifact.type, isParent: true, pathToNode: meta.pathToNode, sourceRange: meta.sourceRange, rotation: artifact.rotation, position: artifact.position, }) } }) }) const parentArtifacts = artifactsWithinCursorRange.filter((a) => a.isParent) const hasSketchArtifact = artifactsWithinCursorRange.filter( ({ parentType }) => parentType === 'sketchGroup' ) const hasExtrudeArtifact = artifactsWithinCursorRange.filter( ({ parentType }) => parentType === 'extrudeGroup' ) const artifact = parentArtifacts[0] const shouldHighlight = !!artifact || hasSketchArtifact.length if ( (guiMode.mode === 'default' || guiMode.mode === 'canEditSketch') && ast && hasSketchArtifact.length ) { const pathToNode = getNodePathFromSourceRange( ast, hasSketchArtifact[0].sourceRange ) const { rotation, position } = hasSketchArtifact[0] setGuiMode({ mode: 'canEditSketch', pathToNode, rotation, position }) } else if ( hasExtrudeArtifact.length && (guiMode.mode === 'default' || guiMode.mode === 'canEditExtrude') && ast ) { const pathToNode = getNodePathFromSourceRange( ast, hasExtrudeArtifact[0].sourceRange ) const { rotation, position } = hasExtrudeArtifact[0] setGuiMode({ mode: 'canEditExtrude', pathToNode, rotation, position }) } else if ( !shouldHighlight && (guiMode.mode === 'canEditExtrude' || guiMode.mode === 'canEditSketch') ) { setGuiMode({ mode: 'default' }) } }, [artifacts, selectionRanges]) }