structuring store
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { customAlphabet } from "nanoid";
|
||||
import { z } from "zod";
|
||||
import { CameraSchema } from "./camera";
|
||||
|
||||
const customId = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 16);
|
||||
|
||||
/**
|
||||
* Material preset name reference
|
||||
* @example 'white', 'brick', 'wood', 'glass', 'preview-valid'
|
||||
*/
|
||||
export const Material = z.string().optional();
|
||||
export const generateId = <T extends string>(prefix: T): `${T}_${string}` =>
|
||||
`${prefix}_${customId()}` as `${T}_${string}`;
|
||||
export const objectId = <T extends string>(prefix: T) => {
|
||||
const schema = z.templateLiteral([`${prefix}_`, z.string()]);
|
||||
|
||||
return schema.default(() => generateId(prefix) as z.infer<typeof schema>);
|
||||
};
|
||||
export const nodeType = <T extends string>(type: T) =>
|
||||
z.literal(type).default(type);
|
||||
|
||||
export const BaseNode = z.object({
|
||||
object: z.literal("node").default("node"),
|
||||
id: z.string(), // objectId('node'), @Aymericr: Thing is if we specify objectId here, when using BaseNode.extend, TS complains that the id is not assignable to the more specific type in the extended node
|
||||
type: nodeType("node"),
|
||||
name: z.string().optional(),
|
||||
parentId: z.string().nullable().default(null),
|
||||
visible: z.boolean().optional().default(true),
|
||||
camera: CameraSchema.optional(),
|
||||
metadata: z.json().optional().default({}),
|
||||
});
|
||||
|
||||
export type BaseNode = z.infer<typeof BaseNode>;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const Vector3Schema = z.tuple([z.number(), z.number(), z.number()]);
|
||||
|
||||
export const CameraSchema = z.object({
|
||||
position: Vector3Schema,
|
||||
target: Vector3Schema,
|
||||
mode: z.enum(["perspective", "orthographic"]).default("perspective"),
|
||||
fov: z.number().optional(), // For perspective
|
||||
zoom: z.number().optional(), // For orthographic
|
||||
});
|
||||
|
||||
export type Camera = z.infer<typeof CameraSchema>;
|
||||
@@ -0,0 +1,21 @@
|
||||
import dedent from "dedent";
|
||||
import { z } from "zod";
|
||||
import { BaseNode, nodeType, objectId } from "../base";
|
||||
import { LevelNode } from "./level";
|
||||
|
||||
export const BuildingNode = BaseNode.extend({
|
||||
id: objectId("building"),
|
||||
type: nodeType("building"),
|
||||
children: z.array(LevelNode).default([LevelNode.parse({})]),
|
||||
position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
|
||||
rotation: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
|
||||
}).describe(
|
||||
dedent`
|
||||
Building node - used to represent a building
|
||||
- position: position in site coordinate system
|
||||
- rotation: rotation in site coordinate system
|
||||
- children: array of level nodes (each level is a tree of floor and wall nodes)
|
||||
`
|
||||
);
|
||||
|
||||
export type BuildingNode = z.infer<typeof BuildingNode>;
|
||||
@@ -0,0 +1,48 @@
|
||||
import dedent from "dedent";
|
||||
import { z } from "zod";
|
||||
import { BaseNode, nodeType, objectId } from "../base";
|
||||
|
||||
export const ItemNode = BaseNode.extend({
|
||||
id: objectId("item"),
|
||||
type: nodeType("item"),
|
||||
position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
|
||||
rotation: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
|
||||
side: z.enum(["front", "back"]).optional(),
|
||||
|
||||
asset: z
|
||||
.object({
|
||||
category: z.string(),
|
||||
src: z.string(),
|
||||
dimensions: z.tuple([z.number(), z.number(), z.number()]), // [w, h, d]
|
||||
attachTo: z.enum(["wall", "wall-side", "ceiling"]).optional(),
|
||||
// These are "Corrective" transforms to normalize the GLB
|
||||
offset: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]),
|
||||
rotation: z
|
||||
.tuple([z.number(), z.number(), z.number()])
|
||||
.default([0, 0, 0]),
|
||||
scale: z
|
||||
.union([z.number(), z.tuple([z.number(), z.number(), z.number()])])
|
||||
.default(1),
|
||||
})
|
||||
.default({
|
||||
category: "",
|
||||
src: "",
|
||||
dimensions: [1, 1, 1],
|
||||
offset: [0, 0, 0],
|
||||
rotation: [0, 0, 0],
|
||||
scale: 1,
|
||||
}),
|
||||
}).describe(dedent`Item node - used to represent a item in the building
|
||||
- position: position in level coordinate system (or parent coordinate system if attached)
|
||||
- rotation: rotation in level coordinate system (or parent coordinate system if attached)
|
||||
- asset: asset data
|
||||
- category: category of the item
|
||||
- dimensions: size in level coordinate system
|
||||
- src: url of the model
|
||||
- attachTo: where to attach the item (wall, wall-side, ceiling)
|
||||
- offset: corrective position offset for the model
|
||||
- rotation: corrective rotation for the model
|
||||
- scale: corrective scale for the model
|
||||
`);
|
||||
|
||||
export type ItemNode = z.infer<typeof ItemNode>;
|
||||
@@ -0,0 +1,20 @@
|
||||
import dedent from "dedent";
|
||||
import { z } from "zod";
|
||||
import { BaseNode, nodeType, objectId } from "../base";
|
||||
import { WallNode } from "./wall";
|
||||
|
||||
export const LevelNode = BaseNode.extend({
|
||||
id: objectId("level"),
|
||||
type: nodeType("level"),
|
||||
children: z.array(z.discriminatedUnion("type", [WallNode])).default([]),
|
||||
// Specific props
|
||||
level: z.number().default(0),
|
||||
}).describe(
|
||||
dedent`
|
||||
Level node - used to represent a level in the building
|
||||
- children: array of floor, wall, ceiling, roof, item nodes
|
||||
- level: level number
|
||||
`
|
||||
);
|
||||
|
||||
export type LevelNode = z.infer<typeof LevelNode>;
|
||||
@@ -0,0 +1,47 @@
|
||||
// lib/scenegraph/schema/nodes/site.ts
|
||||
|
||||
import dedent from "dedent";
|
||||
import { z } from "zod";
|
||||
import { BaseNode, nodeType, objectId } from "../base";
|
||||
import { BuildingNode } from "./building";
|
||||
import { ItemNode } from "./item";
|
||||
|
||||
// 2D Polygon
|
||||
const PropertyLineData = z.object({
|
||||
type: z.literal("polygon"),
|
||||
points: z.array(z.tuple([z.number(), z.number()])),
|
||||
});
|
||||
|
||||
// 3D Polygon/Mesh
|
||||
// const TerrainData = z.object({
|
||||
// type: z.literal('terrain'),
|
||||
// points: z.array(z.tuple([z.number(), z.number(), z.number()])),
|
||||
// })
|
||||
|
||||
export const SiteNode = BaseNode.extend({
|
||||
id: objectId("site"),
|
||||
type: nodeType("site"),
|
||||
// Specific props
|
||||
polygon: PropertyLineData.optional().default({
|
||||
type: "polygon",
|
||||
// Default 30x30 square matching GRID_SIZE
|
||||
points: [
|
||||
[0, 0],
|
||||
[30, 0],
|
||||
[30, 30],
|
||||
[0, 30],
|
||||
],
|
||||
}),
|
||||
// terrain: TerrainData,
|
||||
children: z
|
||||
.array(z.discriminatedUnion("type", [BuildingNode, ItemNode]))
|
||||
.default([BuildingNode.parse({})]),
|
||||
}).describe(
|
||||
dedent`
|
||||
Site node - used to represent a site
|
||||
- polygon: polygon data
|
||||
- children: array of building and item nodes
|
||||
`
|
||||
);
|
||||
|
||||
export type SiteNode = z.infer<typeof SiteNode>;
|
||||
@@ -0,0 +1,32 @@
|
||||
import dedent from "dedent";
|
||||
import { z } from "zod";
|
||||
import { BaseNode, nodeType, objectId } from "../base";
|
||||
// import { DoorNode } from "./door";
|
||||
// import { ItemNode } from "./item";
|
||||
// import { WindowNode } from "./window";
|
||||
|
||||
export const WallNode = BaseNode.extend({
|
||||
id: objectId("wall"),
|
||||
type: nodeType("wall"),
|
||||
// get children() {
|
||||
// return z
|
||||
// .array(z.discriminatedUnion("type", [DoorNode, WindowNode, ItemNode]))
|
||||
// .default([]);
|
||||
// },
|
||||
// Specific props
|
||||
thickness: z.number().optional(),
|
||||
height: z.number().optional(),
|
||||
// e.g., start/end points for path
|
||||
start: z.tuple([z.number(), z.number()]),
|
||||
end: z.tuple([z.number(), z.number()]),
|
||||
}).describe(
|
||||
dedent`
|
||||
Wall node - used to represent a wall in the building
|
||||
- thickness: thickness in meters
|
||||
- height: height in meters
|
||||
- start: start point of the wall in level coordinate system
|
||||
- end: end point of the wall in level coordinate system
|
||||
- size: size of the wall in grid units
|
||||
`
|
||||
);
|
||||
export type WallNode = z.infer<typeof WallNode>;
|
||||
@@ -0,0 +1,16 @@
|
||||
import z from "zod";
|
||||
import { BuildingNode } from "./nodes/building";
|
||||
import { LevelNode } from "./nodes/level";
|
||||
import { SiteNode } from "./nodes/site";
|
||||
import { WallNode } from "./nodes/wall";
|
||||
|
||||
export const AnyNode = z.discriminatedUnion("type", [
|
||||
SiteNode,
|
||||
BuildingNode,
|
||||
LevelNode,
|
||||
WallNode,
|
||||
]);
|
||||
|
||||
export type AnyNode = z.infer<typeof AnyNode>;
|
||||
export type AnyNodeType = AnyNode["type"];
|
||||
export type AnyNodeId = AnyNode["id"];
|
||||
Reference in New Issue
Block a user