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",
"eject": "react-scripts eject"
},
"jest": {
"transformIgnorePatterns": ["node_modules/(?!(three)/)"]
},
"eslintConfig": {
"extends": [
"react-app",
@ -45,6 +48,7 @@
]
},
"devDependencies": {
"@types/three": "^0.146.0",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.19",
"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 { Allotment } from "allotment";
import { OrbitControls, OrthographicCamera } from "@react-three/drei";
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({
from,
to,
}: {
from: [number, number, number];
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>
);
const _code = `sketch mySketch {
path myPath = lineTo(0,1)
lineTo(1,5)
path rightPath = lineTo(1,0)
close()
}
show(mySketch)`;
const OrrthographicCamera = OrthographicCamera as any;
function App() {
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 (
<div className="h-screen">
<Allotment>
<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 className="h-full">
viewer
@ -84,11 +80,16 @@ function App() {
/>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<Box from={[6, 6, 6]} to={[0, 1, 5]} />
<mesh>
<boxGeometry args={[0.1, 0.2, 1]} />
<meshStandardMaterial color={"hotpink"} />
</mesh>
{geoArray.map(
(
{
geo,
sourceRange,
}: { geo: BufferGeometry; sourceRange: [number, number] },
index
) => <Line key={index} geo={geo} sourceRange={sourceRange} />
)}
</Canvas>
</div>
</Allotment>
@ -97,3 +98,28 @@ function 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)
`;
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: "toPoint", to: [0, 1], name: "myPath" },
{ type: "toPoint", to: [1, 1] },
{ type: "toPoint", to: [1, 0], name: "rightPath" },
{ type: "close", firstPath: { type: "base", from: [0, 0] } },
]
);
{ type: "toPoint", to: [0, 1], sourceRange: [25, 45], name: "myPath" },
{ type: "toPoint", to: [1, 1], sourceRange: [48, 59] },
{ type: "toPoint", to: [1, 0], sourceRange: [67, 90], name: "rightPath" },
{
type: "close",
firstPath: { type: "base", from: [0, 0] },
sourceRange: [93, 100],
},
]);
expect(root.mySketch[0]).toEqual(root.mySketch[4].firstPath);
// hmm not sure what handle the "show" function
expect(_return).toEqual([

View File

@ -85,6 +85,7 @@ export const executor = (
const result = sketchFns[fnName](
_programMemory,
variableName,
[declaration.start, declaration.end],
...fnArgs
);
_programMemory._sketch = result.programMemory._sketch;
@ -113,7 +114,7 @@ export const executor = (
`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];
} else if("show" === functionName) {
if (options.bodyType !== "root") {

View File

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

View File

@ -2492,11 +2492,23 @@
dependencies:
"@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":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
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":
version "8.5.3"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d"