This commit is contained in:
wass08
2026-01-21 10:03:29 +09:00
parent 14b0e6a98d
commit 5de0818633
53 changed files with 1241 additions and 1420 deletions
+14 -15
View File
@@ -1,33 +1,32 @@
import { customAlphabet } from "nanoid";
import { z } from "zod";
import { CameraSchema } from "./camera";
import { customAlphabet } from 'nanoid'
import { z } from 'zod'
import { CameraSchema } from './camera'
const customId = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 16);
const customId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 16)
/**
* Material preset name reference
* @example 'white', 'brick', 'wood', 'glass', 'preview-valid'
*/
export const Material = z.string().optional();
export const Material = z.string().optional()
export const generateId = <T extends string>(prefix: T): `${T}_${string}` =>
`${prefix}_${customId()}` as `${T}_${string}`;
`${prefix}_${customId()}` as `${T}_${string}`
export const objectId = <T extends string>(prefix: T) => {
const schema = z.templateLiteral([`${prefix}_`, z.string()]);
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);
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"),
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"),
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>;
export type BaseNode = z.infer<typeof BaseNode>
+5 -5
View File
@@ -1,13 +1,13 @@
import { z } from "zod";
import { z } from 'zod'
const Vector3Schema = z.tuple([z.number(), z.number(), z.number()]);
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"),
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>;
export type Camera = z.infer<typeof CameraSchema>
+13 -16
View File
@@ -1,20 +1,17 @@
// Base
export { BaseNode, generateId, objectId, nodeType, Material } from "./base";
// Nodes
export { SiteNode } from "./nodes/site";
export { BuildingNode } from "./nodes/building";
export { LevelNode } from "./nodes/level";
export { WallNode } from "./nodes/wall";
export { ItemNode } from "./nodes/item";
export type { AssetInput } from "./nodes/item";
// Union types
export { AnyNode } from "./types";
export type { AnyNodeType, AnyNodeId } from "./types";
export { BaseNode, generateId, Material, nodeType, objectId } from './base'
// Camera
export { CameraSchema } from "./camera";
export { CameraSchema } from './camera'
export { BuildingNode } from './nodes/building'
export type { AssetInput } from './nodes/item'
export { ItemNode } from './nodes/item'
export { LevelNode } from './nodes/level'
// Nodes
export { SiteNode } from './nodes/site'
export { WallNode } from './nodes/wall'
export type { AnyNodeId, AnyNodeType } from './types'
// Union types
export { AnyNode } from './types'
// Zones
export type { Zone, ZonePolygon } from "./zone";
export type { Zone, ZonePolygon } from './zone'
+9 -9
View File
@@ -1,11 +1,11 @@
import dedent from "dedent";
import { z } from "zod";
import { BaseNode, nodeType, objectId } from "../base";
import { LevelNode } from "./level";
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"),
id: objectId('building'),
type: nodeType('building'),
children: z.array(LevelNode.shape.id).default([]),
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]),
@@ -15,7 +15,7 @@ export const BuildingNode = BaseNode.extend({
- 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>;
export type BuildingNode = z.infer<typeof BuildingNode>
+12 -12
View File
@@ -1,6 +1,6 @@
import dedent from "dedent";
import { z } from "zod";
import { BaseNode, nodeType, objectId } from "../base";
import dedent from 'dedent'
import { z } from 'zod'
import { BaseNode, nodeType, objectId } from '../base'
const assetSchema = z.object({
id: z.string(),
@@ -9,22 +9,22 @@ const assetSchema = z.object({
thumbnail: z.string(),
src: z.string(),
dimensions: z.tuple([z.number(), z.number(), z.number()]).default([1, 1, 1]), // [w, h, d]
attachTo: z.enum(["wall", "wall-side", "ceiling"]).optional(),
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.tuple([z.number(), z.number(), z.number()]).default([1, 1, 1]),
});
})
export type AssetInput = z.input<typeof assetSchema>;
export type Asset = z.infer<typeof assetSchema>;
export type AssetInput = z.input<typeof assetSchema>
export type Asset = z.infer<typeof assetSchema>
export const ItemNode = BaseNode.extend({
id: objectId("item"),
type: nodeType("item"),
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(),
side: z.enum(['front', 'back']).optional(),
// Wall attachment properties (only used when asset.attachTo is "wall" or "wall-side")
wallId: z.string().optional(),
@@ -42,6 +42,6 @@ export const ItemNode = BaseNode.extend({
- 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>;
export type ItemNode = z.infer<typeof ItemNode>
+9 -9
View File
@@ -1,11 +1,11 @@
import dedent from "dedent";
import { z } from "zod";
import { BaseNode, nodeType, objectId } from "../base";
import { WallNode } from "./wall";
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"),
id: objectId('level'),
type: nodeType('level'),
children: z.array(WallNode.shape.id).default([]),
// Specific props
level: z.number().default(0),
@@ -14,7 +14,7 @@ export const LevelNode = BaseNode.extend({
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>;
export type LevelNode = z.infer<typeof LevelNode>
+14 -14
View File
@@ -1,16 +1,16 @@
// 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";
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"),
type: z.literal('polygon'),
points: z.array(z.tuple([z.number(), z.number()])),
});
})
// 3D Polygon/Mesh
// const TerrainData = z.object({
@@ -19,11 +19,11 @@ const PropertyLineData = z.object({
// })
export const SiteNode = BaseNode.extend({
id: objectId("site"),
type: nodeType("site"),
id: objectId('site'),
type: nodeType('site'),
// Specific props
polygon: PropertyLineData.optional().default({
type: "polygon",
type: 'polygon',
// Default 30x30 square matching GRID_SIZE
points: [
[0, 0],
@@ -34,14 +34,14 @@ export const SiteNode = BaseNode.extend({
}),
// terrain: TerrainData,
children: z
.array(z.discriminatedUnion("type", [BuildingNode, ItemNode]))
.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>;
export type SiteNode = z.infer<typeof SiteNode>
+9 -9
View File
@@ -1,14 +1,14 @@
import dedent from "dedent";
import { z } from "zod";
import { BaseNode, nodeType, objectId } from "../base";
import { ItemNode } from "./item";
import dedent from 'dedent'
import { z } from 'zod'
import { BaseNode, nodeType, objectId } from '../base'
import { ItemNode } from './item'
// import { DoorNode } from "./door";
// import { ItemNode } from "./item";
// import { WindowNode } from "./window";
export const WallNode = BaseNode.extend({
id: objectId("wall"),
type: nodeType("wall"),
id: objectId('wall'),
type: nodeType('wall'),
children: z.array(ItemNode.shape.id).default([]),
// Specific props
thickness: z.number().optional(),
@@ -24,6 +24,6 @@ export const WallNode = BaseNode.extend({
- 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>;
`,
)
export type WallNode = z.infer<typeof WallNode>
+11 -11
View File
@@ -1,18 +1,18 @@
import z from "zod";
import { BuildingNode } from "./nodes/building";
import { ItemNode } from "./nodes/item";
import { LevelNode } from "./nodes/level";
import { SiteNode } from "./nodes/site";
import { WallNode } from "./nodes/wall";
import z from 'zod'
import { BuildingNode } from './nodes/building'
import { ItemNode } from './nodes/item'
import { LevelNode } from './nodes/level'
import { SiteNode } from './nodes/site'
import { WallNode } from './nodes/wall'
export const AnyNode = z.discriminatedUnion("type", [
export const AnyNode = z.discriminatedUnion('type', [
SiteNode,
BuildingNode,
LevelNode,
WallNode,
ItemNode,
]);
])
export type AnyNode = z.infer<typeof AnyNode>;
export type AnyNodeType = AnyNode["type"];
export type AnyNodeId = AnyNode["id"];
export type AnyNode = z.infer<typeof AnyNode>
export type AnyNodeType = AnyNode['type']
export type AnyNodeId = AnyNode['id']
+11 -11
View File
@@ -1,21 +1,21 @@
import dedent from "dedent";
import { z } from "zod";
import { objectId } from "./base";
import { LevelNode } from "./nodes/level";
import dedent from 'dedent'
import { z } from 'zod'
import { objectId } from './base'
import { LevelNode } from './nodes/level'
// Polygon boundary for zone area - array of [x, z] coordinates
export const ZonePolygon = z.array(z.tuple([z.number(), z.number()]));
export const ZonePolygon = z.array(z.tuple([z.number(), z.number()]))
export const ZoneSchema = z
.object({
id: objectId("zone"),
object: z.literal("zone").default("zone"),
id: objectId('zone'),
object: z.literal('zone').default('zone'),
levelId: LevelNode.shape.id, // Required - must be attached to a level
name: z.string(),
// Polygon boundary - array of [x, z] coordinates defining the zone
polygon: ZonePolygon,
// Visual styling
color: z.string().default("#3b82f6"), // Default blue
color: z.string().default('#3b82f6'), // Default blue
metadata: z.json().optional().default({}),
})
.describe(
@@ -29,7 +29,7 @@ export const ZoneSchema = z
- color: hex color for visual styling
- metadata: zone metadata (optional)
`,
);
)
export type Zone = z.infer<typeof ZoneSchema>;
export type ZonePolygon = z.infer<typeof ZonePolygon>;
export type Zone = z.infer<typeof ZoneSchema>
export type ZonePolygon = z.infer<typeof ZonePolygon>