diff --git a/src/App.tsx b/src/App.tsx index dc244fe92..fc020790e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,14 +3,9 @@ import { Canvas } from '@react-three/fiber' import { Allotment } from 'allotment' import { OrbitControls, OrthographicCamera } from '@react-three/drei' import { lexer } from './lang/tokeniser' -import { - abstractSyntaxTree, - getNodePathFromSourceRange, - getNodeFromPath -} from './lang/abstractSyntaxTree' +import { abstractSyntaxTree } from './lang/abstractSyntaxTree' import { executor, processShownObjects, ViewerArtifact } from './lang/executor' import { recast } from './lang/recast' -import { BufferGeometry } from 'three' import CodeMirror from '@uiw/react-codemirror' import { javascript } from '@codemirror/lang-javascript' import { ViewUpdate } from '@codemirror/view' @@ -19,12 +14,12 @@ import { addLineHighlight, } from './editor/highlightextension' import { useStore } from './useStore' -import { isOverlapping } from './lib/utils' import { Toolbar } from './Toolbar' import { BasePlanes } from './components/BasePlanes' import { SketchPlane } from './components/SketchPlane' import { Logs } from './components/Logs' import { AxisIndicator } from './components/AxisIndicator' +import { RenderViewerArtifacts } from './components/SketchLine' const OrrthographicCamera = OrthographicCamera as any @@ -160,7 +155,7 @@ function App() { @@ -199,111 +194,3 @@ function App() { } export default App - -function Line({ - geo, - sourceRange, - forceHighlight = false, -}: { - geo: BufferGeometry - sourceRange: [number, number] - forceHighlight?: boolean -}) { - const { setHighlightRange, selectionRange, guiMode, setGuiMode, ast } = - 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) - const [editorCursor, setEditorCursor] = useState(false) - const [didSetCanEdit, setDidSetCanEdit] = useState(false) - useEffect(() => { - const shouldHighlight = isOverlapping(sourceRange, selectionRange) - setEditorCursor(shouldHighlight) - if (shouldHighlight && guiMode.mode === 'default' && ast) { - const pathToNode = getNodePathFromSourceRange(ast, sourceRange) - const piper = getNodeFromPath(ast, pathToNode, 'PipeExpression') - const axis = piper.type !== 'PipeExpression' ? 'xy' - : piper?.body?.[1]?.callee?.name === 'rx' ? 'xz' : 'yz' - setGuiMode({ mode: 'canEditSketch', pathToNode, axis }) - setDidSetCanEdit(true) - } else if ( - !shouldHighlight && - didSetCanEdit && - guiMode.mode === 'canEditSketch' - ) { - setGuiMode({ mode: 'default' }) - setDidSetCanEdit(false) - } - }, [selectionRange, sourceRange]) - - return ( - { - setHover(true) - setHighlightRange(sourceRange) - }} - onPointerOut={(event) => { - setHover(false) - setHighlightRange([0, 0]) - }} - > - - - - ) -} - -function RenderViewerArtifacts({ - artifact, - forceHighlight = false, -}: { - artifact: ViewerArtifact - forceHighlight?: boolean -}) { - const { selectionRange } = useStore(({ selectionRange }) => ({ - selectionRange, - })) - const [editorCursor, setEditorCursor] = useState(false) - useEffect(() => { - const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange) - setEditorCursor(shouldHighlight) - }, [selectionRange, artifact.sourceRange]) - if (artifact.type === 'sketchLine') { - const { geo, sourceRange } = artifact - return ( - - ) - } - return ( - <> - {artifact.children.map((artifact, index) => ( - - ))} - - ) -} diff --git a/src/components/SketchLine.tsx b/src/components/SketchLine.tsx new file mode 100644 index 000000000..be293de41 --- /dev/null +++ b/src/components/SketchLine.tsx @@ -0,0 +1,274 @@ +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' +import { Vector3 } from 'three' + +function useHeightlight(sourceRange: [number, number]) { + const { selectionRange, guiMode, setGuiMode, ast } = useStore((s) => ({ + setHighlightRange: s.setHighlightRange, + selectionRange: s.selectionRange, + guiMode: s.guiMode, + setGuiMode: s.setGuiMode, + ast: s.ast, + })) + // This reference will give us direct access to the mesh + const [editorCursor, setEditorCursor] = useState(false) + const [didSetCanEdit, setDidSetCanEdit] = useState(false) + useEffect(() => { + const shouldHighlight = isOverlapping(sourceRange, selectionRange) + setEditorCursor(shouldHighlight) + if (shouldHighlight && guiMode.mode === 'default' && ast) { + const pathToNode = getNodePathFromSourceRange(ast, sourceRange) + const piper = getNodeFromPath(ast, pathToNode, 'PipeExpression') + const axis = + piper.type !== 'PipeExpression' + ? 'xy' + : piper?.body?.[1]?.callee?.name === 'rx' + ? 'xz' + : 'yz' + setGuiMode({ mode: 'canEditSketch', pathToNode, axis }) + setDidSetCanEdit(true) + } else if ( + !shouldHighlight && + didSetCanEdit && + guiMode.mode === 'canEditSketch' + ) { + setGuiMode({ mode: 'default' }) + setDidSetCanEdit(false) + } + }, [selectionRange, sourceRange]) + return { + editorCursor, + } +} + +function SketchLine({ + geo, + sourceRange, + forceHighlight = false, +}: { + geo: LineGeos + sourceRange: [number, number] + forceHighlight?: boolean +}) { + const { editorCursor } = useHeightlight(sourceRange) + 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 lastPointerRef = useRef(new Vector3()) + 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 + console.log(callExpression) + return { + originalXY: [x, y], + } + } + return { + originalXY: [-1, -1], + } + }, [ast]) + + useEffect(() => { + const handleMouseUp = () => { + if (isMouseDown && ast) { + const thePath = getNodePathFromSourceRange(ast, sourceRange) + const theNewPoints: [number, number] = [ + roundOff(lastPointerRef.current.x, 2), + roundOff(lastPointerRef.current.y, 2), + ] + console.log('theNewPoints', theNewPoints) + 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]) + + return ( + <> + { + setHover(true) + setHighlightRange(sourceRange) + }} + onPointerOut={(event) => { + setHover(false) + setHighlightRange([0, 0]) + }} + onPointerDown={() => setIsMouseDown(true)} + > + + + + {isMouseDown && ( + { + const point = a.point + 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" + > + + + + )} + + ) +} + +export function RenderViewerArtifacts({ + artifact, + forceHighlight = false, +}: { + artifact: ViewerArtifact + forceHighlight?: boolean +}) { + const { selectionRange } = useStore(({ selectionRange }) => ({ + selectionRange, + })) + const [editorCursor, setEditorCursor] = useState(false) + useEffect(() => { + const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange) + setEditorCursor(shouldHighlight) + }, [selectionRange, artifact.sourceRange]) + if (artifact.type === 'sketchLine') { + const { geo, sourceRange } = artifact + return ( + + ) + } + if (artifact.type === 'sketchBase') { + console.log('BASE TODO') + return null + } + return ( + <> + {artifact.children.map((artifact, index) => ( + + ))} + + ) +} diff --git a/src/lang/abstractSyntaxTree.ts b/src/lang/abstractSyntaxTree.ts index 66442e381..47fe73af7 100644 --- a/src/lang/abstractSyntaxTree.ts +++ b/src/lang/abstractSyntaxTree.ts @@ -1215,6 +1215,38 @@ export function addLine( } } +export function changeArguments( + node: Program, + pathToNode: (string | number)[], + args: [number, number] +): { modifiedAst: Program; pathToNode: (string | number)[] }{ + const _node = { ...node } + const dumbyStartend = { start: 0, end: 0 } + // const thePath = getNodePathFromSourceRange(_node, sourceRange) + const callExpression = getNodeFromPath(_node, pathToNode) as CallExpression + const newXArg: CallExpression['arguments'][number] = callExpression.arguments[0].type === 'Literal' ? { + type: 'Literal', + ...dumbyStartend, + value: args[0], + raw: `${args[0]}`, + } : { + ...callExpression.arguments[0] + } + const newYArg: CallExpression['arguments'][number] = callExpression.arguments[1].type === 'Literal' ? { + type: 'Literal', + ...dumbyStartend, + value: args[1], + raw: `${args[1]}`, + } : { + ...callExpression.arguments[1] + } + callExpression.arguments = [newXArg, newYArg] + return { + modifiedAst: _node, + pathToNode, + } +} + function isCallExpression(tokens: Token[], index: number): number { const currentToken = tokens[index] const veryNextToken = tokens[index + 1] // i.e. no whitespace diff --git a/src/lang/engine.tsx b/src/lang/engine.tsx index 517d3e494..38bfc7c79 100644 --- a/src/lang/engine.tsx +++ b/src/lang/engine.tsx @@ -7,15 +7,21 @@ export function baseGeo({ from }: { from: [number, number, number] }) { return baseSphere } +export interface LineGeos { + line: BufferGeometry + tip: BufferGeometry + centre: BufferGeometry +} + export function lineGeo({ from, to, }: { from: [number, number, number] to: [number, number, number] -}): BufferGeometry { +}): LineGeos { const sq = (a: number): number => a * a - const center = [ + const centre = [ (from[0] + to[0]) / 2, (from[1] + to[1]) / 2, (from[2] + to[2]) / 2, @@ -34,15 +40,23 @@ export function lineGeo({ const lineBody = new BoxGeometry(Hypotenuse3d, 0.1, 0.1) lineBody.rotateY(ang1) lineBody.rotateZ(ang2) - lineBody.translate(center[0], center[1], center[2]) + lineBody.translate(centre[0], centre[1], centre[2]) // create line end balls with SphereGeometry at `to` and `from` with radius of 0.15 const lineEnd1 = new SphereGeometry(0.15) lineEnd1.translate(to[0], to[1], to[2]) + + const centreSphere = new SphereGeometry(0.15) + centreSphere.translate(centre[0], centre[1], centre[2]) // const lineEnd2 = new SphereGeometry(0.15); // lineEnd2.translate(from[0], from[1], from[2]) // group all three geometries - return mergeBufferGeometries([lineBody, lineEnd1]) + // return mergeBufferGeometries([lineBody, lineEnd1]) // return mergeBufferGeometries([lineBody, lineEnd1, lineEnd2]); + return { + line: lineBody, + tip: lineEnd1, + centre: centreSphere, + } } diff --git a/src/lang/executor.ts b/src/lang/executor.ts index 6a76e87ef..aaed9f76b 100644 --- a/src/lang/executor.ts +++ b/src/lang/executor.ts @@ -6,6 +6,7 @@ import { } from './abstractSyntaxTree' import { Path, Transform, SketchGeo, sketchFns } from './sketch' import { BufferGeometry } from 'three' +import { LineGeos } from './engine' export interface ProgramMemory { root: { [key: string]: any } @@ -296,8 +297,13 @@ export type ViewerArtifact = | { type: 'sketchLine' sourceRange: SourceRange - geo: BufferGeometry + geo: LineGeos } + | { + type: 'sketchBase', + sourceRange: SourceRange, + geo: BufferGeometry + } | { type: 'parent' sourceRange: SourceRange @@ -315,20 +321,44 @@ export const processShownObjects = ( previousTransforms: PreviousTransforms = [] ): ViewerArtifact[] => { if (geoMeta?.type === 'sketchGeo') { - return geoMeta.sketch.map(({ geo, sourceRange }) => { - const newGeo = geo.clone() - previousTransforms.forEach(({ rotation, transform }) => { - newGeo.rotateX(rotation[0]) - newGeo.rotateY(rotation[1]) - newGeo.rotateZ(rotation[2]) - newGeo.translate(transform[0], transform[1], transform[2]) - }) - - return { - type: 'sketchLine', - geo: newGeo, - sourceRange, + return geoMeta.sketch.map(({ geo, sourceRange, type }) => { + if(type === 'toPoint') { + // const newGeo = geo.clone() + const newGeo: LineGeos = { + line: geo.line.clone(), + tip: geo.tip.clone(), + centre: geo.centre.clone(), + } + previousTransforms.forEach(({ rotation, transform }) => { + Object.values(newGeo).forEach((geoItem) => { + geoItem.rotateX(rotation[0]) + geoItem.rotateY(rotation[1]) + geoItem.rotateZ(rotation[2]) + geoItem.translate(transform[0], transform[1], transform[2]) + }) + }) + return { + type: 'sketchLine', + geo: newGeo, + sourceRange, + } + } else if(type === 'base') { + const newGeo = geo.clone() + previousTransforms.forEach(({ rotation, transform }) => { + newGeo.rotateX(rotation[0]) + newGeo.rotateY(rotation[1]) + newGeo.rotateZ(rotation[2]) + newGeo.translate(transform[0], transform[1], transform[2]) + }) + return { + type: 'sketchBase', + geo: newGeo, + sourceRange, + } } + console.log('type',type) + + throw new Error('Unknown geo type') }) } else if (geoMeta.type === 'transform') { const referencedVar = geoMeta.sketch diff --git a/src/lang/sketch.ts b/src/lang/sketch.ts index d6f1e5e72..3a7869714 100644 --- a/src/lang/sketch.ts +++ b/src/lang/sketch.ts @@ -1,5 +1,5 @@ import { ProgramMemory } from './executor' -import { lineGeo, baseGeo } from './engine' +import { lineGeo, baseGeo, LineGeos } from './engine' import { BufferGeometry } from 'three' type Coords2d = [number, number] @@ -37,7 +37,7 @@ export type Path = name?: string to: Coords2d previousPath: Path - geo: BufferGeometry + geo: LineGeos sourceRange: SourceRange } | { @@ -45,7 +45,7 @@ export type Path = name?: string firstPath: Path previousPath: Path - geo: BufferGeometry + geo: LineGeos sourceRange: SourceRange } | {