import { useRef, useState, useEffect, useMemo } from 'react' import { getNodePathFromSourceRange, getNodeFromPath, CallExpression, } from '../lang/abstractSyntaxTree' import { changeArguments } from '../lang/modifyAst' import { ExtrudeGroup, ExtrudeSurface, SketchGroup, Path, Rotation, Position, PathToNode, SourceRange, } from '../lang/executor' import { BufferGeometry } from 'three' import { useStore } from '../useStore' import { isOverlapping } from '../lib/utils' import { Vector3, DoubleSide, Quaternion } from 'three' import { useSetCursor } from '../hooks/useSetCursor' const roundOff = (num: number, places: number): number => { const x = Math.pow(10, places) return Math.round(num * x) / x } function MovingSphere({ geo, sourceRange, editorCursor, rotation, position, }: { geo: BufferGeometry sourceRange: [number, number] editorCursor: boolean rotation: Rotation position: Position }) { 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 } = 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) const yo = point2DRef.current.clone() const inverseQuaternion = new Quaternion() if ( guiMode.mode === 'canEditSketch' || (guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit') ) { inverseQuaternion.set(...guiMode.rotation) inverseQuaternion.invert() } yo.sub(new Vector3(...position).applyQuaternion(inverseQuaternion)) let [x, y] = [roundOff(yo.x, 2), roundOff(yo.y, 2)] let theNewPoints: [number, number] = [x, y] const { modifiedAst } = changeArguments(ast, thePath, theNewPoints) 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, selectionRange } = useStore( ({ setHighlightRange, selectionRange }) => ({ setHighlightRange, selectionRange, }) ) 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 = isOverlapping( geoInfo.__geoMeta.sourceRange, selectionRange ) setEditorCursor(shouldHighlight) }, [selectionRange, 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 { selectionRange } = useStore(({ selectionRange }) => ({ selectionRange, })) const [editorCursor, setEditorCursor] = useState(false) useEffect(() => { const shouldHighlight = isOverlapping( geoInfo.__geoMeta.sourceRange, selectionRange ) setEditorCursor(shouldHighlight) }, [selectionRange, 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(({ setHighlightRange }) => ({ 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 Boop = ExtrudeGroup | SketchGroup function useSetAppModeFromCursorLocation(artifacts: Boop[]) { const { selectionRange, guiMode, setGuiMode, ast } = useStore( ({ selectionRange, guiMode, setGuiMode, ast }) => ({ selectionRange, guiMode, setGuiMode, ast, }) ) useEffect(() => { const artifactsWithinCursorRange: ( | { parentType: Boop['type'] isParent: true pathToNode: PathToNode sourceRange: SourceRange rotation: Rotation position: Position } | { parentType: Boop['type'] isParent: false pathToNode: PathToNode sourceRange: SourceRange } )[] = [] artifacts?.forEach((artifact) => { artifact.value.forEach((geo) => { if (isOverlapping(geo.__geoMeta.sourceRange, selectionRange)) { artifactsWithinCursorRange.push({ parentType: artifact.type, isParent: false, pathToNode: geo.__geoMeta.pathToNode, sourceRange: geo.__geoMeta.sourceRange, }) } }) artifact.__meta.forEach((meta) => { if (isOverlapping(meta.sourceRange, selectionRange)) { 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) if (parentArtifacts.length > 1) { console.log('multiple parents, might be an issue?', parentArtifacts) } const artifact = parentArtifacts[0] const shouldHighlight = !!artifact if ( shouldHighlight && (guiMode.mode === 'default' || guiMode.mode === 'canEditSketch') && ast && artifact.parentType === 'sketchGroup' && artifact.isParent ) { const pathToNode = getNodePathFromSourceRange(ast, artifact.sourceRange) const { rotation, position } = artifact setGuiMode({ mode: 'canEditSketch', pathToNode, rotation, position }) } else if ( shouldHighlight && (guiMode.mode === 'default' || guiMode.mode === 'canEditSketch') && ast && artifact.parentType === 'extrudeGroup' && artifact.isParent ) { const pathToNode = getNodePathFromSourceRange(ast, artifact.sourceRange) const { rotation, position } = artifact setGuiMode({ mode: 'canEditExtrude', pathToNode, rotation, position }) } else if ( !shouldHighlight && (guiMode.mode === 'canEditSketch' || guiMode.mode === 'canEditExtrude') // (artifact.parentType === 'extrudeGroup' || artifact.type === 'extrudeGroup') ) { setGuiMode({ mode: 'default' }) } }, [artifacts, selectionRange]) }