sketch code to 3d scene initial connection working

This commit is contained in:
Kurt Hutten IrevDev
2022-11-23 21:28:38 +11:00
parent 266edb578a
commit 5ce89d83fc
7 changed files with 204 additions and 71 deletions

View File

@ -26,6 +26,9 @@
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"jest": {
"transformIgnorePatterns": ["node_modules/(?!(three)/)"]
},
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
"react-app", "react-app",
@ -45,6 +48,7 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"@types/three": "^0.146.0",
"autoprefixer": "^10.4.13", "autoprefixer": "^10.4.13",
"postcss": "^8.4.19", "postcss": "^8.4.19",
"tailwindcss": "^3.2.4" "tailwindcss": "^3.2.4"

View File

@ -1,69 +1,65 @@
import React, { useRef, useState } from "react"; import React, { useRef, useState, useEffect } from "react";
import { Canvas } from "@react-three/fiber"; import { Canvas } from "@react-three/fiber";
import { Allotment } from "allotment"; import { Allotment } from "allotment";
import { OrbitControls, OrthographicCamera } from "@react-three/drei"; import { OrbitControls, OrthographicCamera } from "@react-three/drei";
import "allotment/dist/style.css"; import "allotment/dist/style.css";
import { lexer } from "./lang/tokeniser";
import { abstractSyntaxTree } from "./lang/abstractSyntaxTree";
import { executor } from "./lang/executor";
import { BufferGeometry } from "three";
// import { Box } from "./lang/engine";
function Box({ const _code = `sketch mySketch {
from, path myPath = lineTo(0,1)
to, lineTo(1,5)
}: { path rightPath = lineTo(1,0)
from: [number, number, number]; close()
to: [number, number, number];
}) {
// This reference will give us direct access to the mesh
const mesh = useRef<{ rotation: { x: number } }>();
// Set up state for the hovered and active state
const [hovered, setHover] = useState(false);
const [active, setActive] = useState(false);
const sq = (a: number): number => a * a;
const center = [
(from[0] + to[0]) / 2,
(from[1] + to[1]) / 2,
(from[2] + to[2]) / 2,
];
const Hypotenuse3d = Math.sqrt(
sq(from[0] - to[0]) + sq(from[1] - to[1]) + sq(from[2] - to[2])
);
const ang1 = -Math.atan2(from[2] - to[2], from[0] - to[0]);
const Hypotenuse2d = Math.sqrt(sq(from[0] - to[0]) + sq(from[2] - to[2]));
const ang2 = -Math.atan2(to[1] - from[1], Hypotenuse2d);
return (
<group>
<mesh
position={center}
rotation={[0, ang1, ang2]}
ref={mesh}
onClick={(event) => setActive(!active)}
onPointerOver={(event) => setHover(true)}
onPointerOut={(event) => setHover(false)}
>
<boxGeometry args={[Hypotenuse3d, 0.1, 0.1]} />
<meshStandardMaterial color={hovered ? "hotpink" : "orange"} />
</mesh>
<mesh position={to}>
<sphereGeometry args={[0.15, 8, 8]} />
<meshStandardMaterial color={hovered ? "hotpink" : "orange"} />
</mesh>
<mesh position={from}>
<sphereGeometry args={[0.15, 8, 8]} />
<meshStandardMaterial color={hovered ? "hotpink" : "orange"} />
</mesh>
</group>
);
} }
show(mySketch)`;
const OrrthographicCamera = OrthographicCamera as any; const OrrthographicCamera = OrthographicCamera as any;
function App() { function App() {
const cam = useRef(); const cam = useRef();
const [code, setCode] = useState(_code);
const [geoArray, setGeoArray] = useState<
{ geo: BufferGeometry; sourceRange: [number, number] }[]
>([]);
useEffect(() => {
try {
const tokens = lexer(code);
const ast = abstractSyntaxTree(tokens);
const programMemory = executor(ast);
const geos: { geo: BufferGeometry; sourceRange: [number, number] }[] =
programMemory.root.mySketch
.map(
({
geo,
sourceRange,
}: {
geo: BufferGeometry;
sourceRange: [number, number];
}) => ({ geo, sourceRange })
)
.filter((a: any) => !!a.geo);
setGeoArray(geos);
console.log("length", geos.length, geos);
console.log(programMemory);
} catch (e) {
console.log(e);
}
}, [code]);
return ( return (
<div className="h-screen"> <div className="h-screen">
<Allotment> <Allotment>
<div className="bg-red h-full"> <div className="bg-red h-full">
editor <textarea
<textarea /> className="w-full p-4 h-64 font-mono"
onChange={(a) => setCode(a.target.value)}
value={code}
>
{code}
</textarea>
</div> </div>
<div className="h-full"> <div className="h-full">
viewer viewer
@ -84,11 +80,16 @@ function App() {
/> />
<ambientLight /> <ambientLight />
<pointLight position={[10, 10, 10]} /> <pointLight position={[10, 10, 10]} />
<Box from={[6, 6, 6]} to={[0, 1, 5]} /> {geoArray.map(
<mesh> (
<boxGeometry args={[0.1, 0.2, 1]} /> {
<meshStandardMaterial color={"hotpink"} /> geo,
</mesh> sourceRange,
}: { geo: BufferGeometry; sourceRange: [number, number] },
index
) => <Line key={index} geo={geo} sourceRange={sourceRange} />
)}
</Canvas> </Canvas>
</div> </div>
</Allotment> </Allotment>
@ -97,3 +98,28 @@ function App() {
} }
export default App; export default App;
function Line({
geo,
sourceRange,
}: {
geo: BufferGeometry;
sourceRange: [number, number];
}) {
// This reference will give us direct access to the mesh
// const ref = useRef<Mesh<BufferGeometry | Material | Material[]> | undefined>();
const ref = useRef<BufferGeometry | undefined>() as any;
// Set up state for the hovered and active state
const [hovered, setHover] = useState(false);
return (
<mesh
ref={ref}
onPointerOver={(event) => setHover(true)}
onPointerOut={(event) => setHover(false)}
>
<primitive object={geo} />
<meshStandardMaterial color={hovered ? "hotpink" : "orange"} />
</mesh>
);
}

40
src/lang/engine.tsx Normal file
View File

@ -0,0 +1,40 @@
import { BoxGeometry, SphereGeometry, BufferGeometry } from 'three'
import {mergeBufferGeometries} from 'three/examples/jsm/utils/BufferGeometryUtils'
export function lineGeo({
from,
to,
}: {
from: [number, number, number];
to: [number, number, number];
}): BufferGeometry {
const sq = (a: number): number => a * a;
const center = [
(from[0] + to[0]) / 2,
(from[1] + to[1]) / 2,
(from[2] + to[2]) / 2,
];
const Hypotenuse3d = Math.sqrt(
sq(from[0] - to[0]) + sq(from[1] - to[1]) + sq(from[2] - to[2])
);
const ang1 = Math.atan2(from[2] - to[2], from[0] - to[0]);
const Hypotenuse2d = Math.sqrt(sq(from[0] - to[0]) + sq(from[2] - to[2]));
const ang2 = Math.abs(Math.atan((to[1] - from[1])/ Hypotenuse2d))*Math.sign(to[1] - from[1])*(Math.sign(to[0] - from[0])||1);
// create BoxGeometry with size [Hypotenuse3d, 0.1, 0.1] centered at center, with rotation of [0, ang1, ang2]
const lineBody = new BoxGeometry(Hypotenuse3d, 0.1, 0.1);
lineBody.rotateY(ang1)
lineBody.rotateZ(ang2)
lineBody.translate(center[0], center[1], center[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 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, lineEnd2]);
}

View File

@ -62,15 +62,19 @@ log(5, myVar)`;
show(mySketch) show(mySketch)
`; `;
const { root, return: _return } = exe(code); const { root, return: _return } = exe(code);
expect(root.mySketch.map(({ previousPath, ...rest }: any) => rest)).toEqual( expect(
[ root.mySketch.map(({ previousPath, geo, ...rest }: any) => rest)
).toEqual([
{ type: "base", from: [0, 0] }, { type: "base", from: [0, 0] },
{ type: "toPoint", to: [0, 1], name: "myPath" }, { type: "toPoint", to: [0, 1], sourceRange: [25, 45], name: "myPath" },
{ type: "toPoint", to: [1, 1] }, { type: "toPoint", to: [1, 1], sourceRange: [48, 59] },
{ type: "toPoint", to: [1, 0], name: "rightPath" }, { type: "toPoint", to: [1, 0], sourceRange: [67, 90], name: "rightPath" },
{ type: "close", firstPath: { type: "base", from: [0, 0] } }, {
] type: "close",
); firstPath: { type: "base", from: [0, 0] },
sourceRange: [93, 100],
},
]);
expect(root.mySketch[0]).toEqual(root.mySketch[4].firstPath); expect(root.mySketch[0]).toEqual(root.mySketch[4].firstPath);
// hmm not sure what handle the "show" function // hmm not sure what handle the "show" function
expect(_return).toEqual([ expect(_return).toEqual([

View File

@ -85,6 +85,7 @@ export const executor = (
const result = sketchFns[fnName]( const result = sketchFns[fnName](
_programMemory, _programMemory,
variableName, variableName,
[declaration.start, declaration.end],
...fnArgs ...fnArgs
); );
_programMemory._sketch = result.programMemory._sketch; _programMemory._sketch = result.programMemory._sketch;
@ -113,7 +114,7 @@ export const executor = (
`Cannot call ${functionName} outside of a sketch declaration` `Cannot call ${functionName} outside of a sketch declaration`
); );
} }
const result = sketchFns[functionName](_programMemory, "", ...args); const result = sketchFns[functionName](_programMemory, "", [statement.start, statement.end], ...args);
_programMemory._sketch = [...result.programMemory._sketch]; _programMemory._sketch = [...result.programMemory._sketch];
} else if("show" === functionName) { } else if("show" === functionName) {
if (options.bodyType !== "root") { if (options.bodyType !== "root") {

View File

@ -1,39 +1,54 @@
import { ProgramMemory } from "./executor"; import { ProgramMemory } from "./executor";
import { lineGeo } from "./engine";
import { BufferGeometry } from 'three'
type Coords2d = [number, number]
type SourceRange = [number, number]
export type Path = export type Path =
| { | {
type: "points"; type: "points";
name?: string; name?: string;
from: [number, number]; from: Coords2d;
to: [number, number]; to: Coords2d;
geo: BufferGeometry;
sourceRange: SourceRange;
} }
| { | {
type: "horizontalLineTo"; type: "horizontalLineTo";
name?: string; name?: string;
x: number; x: number;
previousPath: Path; previousPath: Path;
geo: BufferGeometry;
sourceRange: SourceRange;
} }
| { | {
type: "verticalLineTo"; type: "verticalLineTo";
name?: string; name?: string;
y: number; y: number;
previousPath: Path; previousPath: Path;
geo: BufferGeometry;
sourceRange: SourceRange;
} }
| { | {
type: "toPoint"; type: "toPoint";
name?: string; name?: string;
to: [number, number]; to: Coords2d;
previousPath: Path; previousPath: Path;
geo: BufferGeometry;
sourceRange: SourceRange;
} }
| { | {
type: "close"; type: "close";
name?: string; name?: string;
firstPath: Path; firstPath: Path;
previousPath: Path; previousPath: Path;
geo: BufferGeometry;
sourceRange: SourceRange;
} }
| { | {
type: "base"; type: "base";
from: [number, number]; from: Coords2d;
}; };
function addBasePath(programMemory: ProgramMemory) { function addBasePath(programMemory: ProgramMemory) {
@ -55,19 +70,46 @@ interface PathReturn {
currentPath: Path; currentPath: Path;
} }
function getCoordsFromPaths(paths: Path[], index = 0): Coords2d {
const currentPath = paths[index]
if(!currentPath) {
return [0, 0]
}
if(currentPath.type === 'points' || currentPath.type === 'toPoint') {
return currentPath.to
} else if (currentPath.type === 'base') {
return currentPath.from
} else if (currentPath.type === 'horizontalLineTo') {
const pathBefore = getCoordsFromPaths(paths, index - 1)
return [currentPath.x, pathBefore[1]]
} else if (currentPath.type === 'verticalLineTo') {
const pathBefore = getCoordsFromPaths(paths, index - 1)
return [pathBefore[0], currentPath.y]
}
return [0,0]
}
export const sketchFns = { export const sketchFns = {
close: (programMemory: ProgramMemory, name: string = ""): PathReturn => { close: (programMemory: ProgramMemory, name: string = "", sourceRange: SourceRange): PathReturn => {
const lastPath = programMemory?._sketch?.[ const lastPath = programMemory?._sketch?.[
programMemory?._sketch.length - 1 programMemory?._sketch.length - 1
] as Path; ] as Path;
let from = getCoordsFromPaths(programMemory?._sketch, programMemory?._sketch.length - 1)
const firstPath = programMemory?._sketch?.[0] as Path; const firstPath = programMemory?._sketch?.[0] as Path;
if (lastPath?.type === "base") { if (lastPath?.type === "base") {
throw new Error("Cannot close a base path"); throw new Error("Cannot close a base path");
} }
let to = getCoordsFromPaths(programMemory?._sketch, 0)
const newPath: Path = { const newPath: Path = {
type: "close", type: "close",
firstPath, firstPath,
previousPath: lastPath, previousPath: lastPath,
geo: lineGeo({from: [...from, 0], to: [...to, 0]}),
sourceRange
}; };
if (name) { if (name) {
newPath.name = name; newPath.name = name;
@ -83,6 +125,7 @@ export const sketchFns = {
lineTo: ( lineTo: (
programMemory: ProgramMemory, programMemory: ProgramMemory,
name: string = "", name: string = "",
sourceRange: SourceRange,
...args: any[] ...args: any[]
): PathReturn => { ): PathReturn => {
const _programMemory = addBasePath(programMemory); const _programMemory = addBasePath(programMemory);
@ -92,10 +135,13 @@ export const sketchFns = {
} }
const lastPath: Path = const lastPath: Path =
_programMemory._sketch[_programMemory._sketch.length - 1]; _programMemory._sketch[_programMemory._sketch.length - 1];
let from = getCoordsFromPaths(programMemory?._sketch, programMemory?._sketch.length - 1)
const currentPath: Path = { const currentPath: Path = {
type: "toPoint", type: "toPoint",
to: [x, y], to: [x, y],
previousPath: lastPath, previousPath: lastPath,
geo: lineGeo({from: [...from, 0], to: [x, y, 0]}),
sourceRange
}; };
if (name) { if (name) {
currentPath.name = name; currentPath.name = name;

View File

@ -2492,11 +2492,23 @@
dependencies: dependencies:
"@types/jest" "*" "@types/jest" "*"
"@types/three@^0.146.0":
version "0.146.0"
resolved "https://registry.yarnpkg.com/@types/three/-/three-0.146.0.tgz#83813ba0d2fff6bdc6d7fda3a77993a932bba45f"
integrity sha512-75AgysUrIvTCB054eQa2pDVFurfeFW8CrMQjpzjt3yHBfuuknoSvvsESd/3EhQxPrz9si3+P0wiDUVsWUlljfA==
dependencies:
"@types/webxr" "*"
"@types/trusted-types@^2.0.2": "@types/trusted-types@^2.0.2":
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
"@types/webxr@*":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.0.tgz#aae1cef3210d88fd4204f8c33385a0bbc4da07c9"
integrity sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA==
"@types/ws@^8.5.1": "@types/ws@^8.5.1":
version "8.5.3" version "8.5.3"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"