From eb494752195b2dcb2c8773e17b9efec8b7e9b8eb Mon Sep 17 00:00:00 2001 From: Thomas Neil James Shadwell Date: Wed, 25 Dec 2024 16:24:07 +0000 Subject: [PATCH] zemn.me: 3d scene renderer --- .../zemn.me/app/experiments/3d/BUILD.bazel | 26 ++++ project/zemn.me/app/experiments/3d/client.tsx | 142 ++++++++++++++++++ project/zemn.me/app/experiments/3d/page.tsx | 15 ++ project/zemn.me/app/experiments/BUILD.bazel | 1 + ts/math/cartesian.ts | 4 + ts/math/mesh/mesh.ts | 4 +- 6 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 project/zemn.me/app/experiments/3d/BUILD.bazel create mode 100644 project/zemn.me/app/experiments/3d/client.tsx create mode 100644 project/zemn.me/app/experiments/3d/page.tsx diff --git a/project/zemn.me/app/experiments/3d/BUILD.bazel b/project/zemn.me/app/experiments/3d/BUILD.bazel new file mode 100644 index 0000000000..72a97bc495 --- /dev/null +++ b/project/zemn.me/app/experiments/3d/BUILD.bazel @@ -0,0 +1,26 @@ +load("//bzl:rules.bzl", "bazel_lint") +load("//ts:rules.bzl", "ts_project") + +ts_project( + name = "3d", + srcs = [ + "client.tsx", + "page.tsx", + ], + visibility = ["//project/zemn.me/app/experiments:__subpackages__"], + deps = [ + "//:node_modules/@types/react", + "//:node_modules/@types/react-dom", + "//:node_modules/d3-array", + "//:node_modules/next", + "//:node_modules/react", + "//:node_modules/react-dom", + "//ts/math", + "//ts/math/mesh", + ], +) + +bazel_lint( + name = "bazel_lint", + srcs = ["BUILD.bazel"], +) diff --git a/project/zemn.me/app/experiments/3d/client.tsx b/project/zemn.me/app/experiments/3d/client.tsx new file mode 100644 index 0000000000..176ca4a05a --- /dev/null +++ b/project/zemn.me/app/experiments/3d/client.tsx @@ -0,0 +1,142 @@ +"use client"; +import { extent } from "d3-array"; +import React, { Fragment, useId, useState } from "react"; + +import { Iterable } from "#root/ts/iter/index.js"; +import { camera } from "#root/ts/math/camera.js"; +import { clamp3DTo2D, Line2D, Line3D, point, Point3D, x, y } from "#root/ts/math/cartesian.js"; +import { Centre, cube, Mesh, mesh2Edges } from "#root/ts/math/mesh/mesh.js"; + +interface Camera { + position: Point3D + lookAt: Point3D +} + +interface EdgesRendererProps { + readonly edges: Line2D<2>[] +} + +function EdgesRenderer(props: EdgesRendererProps) { + const { edges } = props; + + // Compute the viewBox from the extents of the edges + const [xmin, xmax] = extent(edges.map(l => l.map(pt => x(pt))).flat(2))!; + const [ymin, ymax] = extent(edges.map(l => l.map(pt => y(pt))).flat(2))!; + + return ( + <> + {Object.entries({ xmin, xmax, ymin, ymax }).map(([k, v]) => <> + {k}: {v}{" "})} + + {edges.map((edge, i) => { + const [start, end] = edge; + return ( + + ); + })} + + + ); +} + +interface Scene { + readonly objects: Object[] + readonly edges: (v: Object) => globalThis.Iterable> + readonly camera: Camera +} + +interface MapProps { + className?: string + scene: Scene +} + +function Map(p: MapProps) { + const cameraViewLine: Line3D<2> = [ + p.scene.camera.position, + p.scene.camera.lookAt + ]; + + const meshesEdges = + Iterable(p.scene.objects) + .map(p.scene.edges) + .flatten(); + + const lines: Line3D<2>[] = [ + cameraViewLine, + ...meshesEdges.to_array() + ]; + + // Convert 3D lines to 2D + const edges2D: EdgesRendererProps["edges"] = lines.map(([s, e]) => [ + clamp3DTo2D(s), clamp3DTo2D(e) + ]) as Line2D<2>[]; + + return ; +} + +function Projection(p: MapProps) { + return => + [ + camera( + p.scene.camera.position, + p.scene.camera.lookAt, + start, + 4, + ), + camera( + p.scene.camera.position, + p.scene.camera.lookAt, + end, + 4, + ), + ]).to_array() + } /> + +} + + +export function SceneRenderer(props: Scene) { + return
+ {/* top down map */} + + {/* projection */} + +
+} + +export function ThreeDClient() { + const subject = cube(point<3>(0, 0, 0), 1); + const xyz = [1, 5, 2].map(v => useState(v)); + + return <> + {xyz.map(([g, s], i) => + + s(+t.target.value)} + type="range" value={g} /> + + {g} + + )} + ( + ...xyz.map(([g]) => g) as [number, number, number] + ) + }} + edges={(v: Mesh<8>) => mesh2Edges(v)} + objects={[subject]} + />; +} diff --git a/project/zemn.me/app/experiments/3d/page.tsx b/project/zemn.me/app/experiments/3d/page.tsx new file mode 100644 index 0000000000..97e49cda43 --- /dev/null +++ b/project/zemn.me/app/experiments/3d/page.tsx @@ -0,0 +1,15 @@ +import { Metadata } from 'next/types'; + +import { ThreeDClient } from '#root/project/zemn.me/app/experiments/3d/client.js'; + + + +export default function Page() { + return +} + +export const metadata: Metadata = { + title: "test 3d renderer" +} + + diff --git a/project/zemn.me/app/experiments/BUILD.bazel b/project/zemn.me/app/experiments/BUILD.bazel index 5031d787a4..bf2f171b80 100644 --- a/project/zemn.me/app/experiments/BUILD.bazel +++ b/project/zemn.me/app/experiments/BUILD.bazel @@ -15,6 +15,7 @@ ts_project( "//:node_modules/nuqs", "//:node_modules/react", "//:node_modules/seedrandom", + "//project/zemn.me/app/experiments/3d", "//project/zemn.me/app/experiments/article", "//project/zemn.me/app/experiments/cultist", "//project/zemn.me/app/experiments/factorio", diff --git a/ts/math/cartesian.ts b/ts/math/cartesian.ts index fab36236f2..fd7cdf090b 100644 --- a/ts/math/cartesian.ts +++ b/ts/math/cartesian.ts @@ -64,6 +64,10 @@ export function length(l: Line2D<2>): number { return magnitude(Matrix.sub<1, 2>(l[1], l[0])); } +export function clamp3DTo2D(pt: Point<3>): Point<2> { + return point<2>(x(pt), y(pt)) +} + export function magnitude(l: Point): number { return vecMag(l.map(([v]) => v)) } diff --git a/ts/math/mesh/mesh.ts b/ts/math/mesh/mesh.ts index 0da5f2a925..edc9833a58 100644 --- a/ts/math/mesh/mesh.ts +++ b/ts/math/mesh/mesh.ts @@ -29,8 +29,8 @@ export interface Mesh { } export function mesh2Edges(m: Mesh) { - return map(m[Edges], ([start, end]) => - [m[Verticies][start], m[Verticies][end]] as const + return map(m[Edges], ([start, end]): Cartesian.Line3D<2> => + [m[Verticies][start]!, m[Verticies][end]!] ) }