@rooms.sh/sdk 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +344 -0
- package/dist/index.js +465 -0
- package/package.json +32 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
declare const ROOM_FORMAT_VERSION: 1;
|
|
2
|
+
type Vec2 = [number, number];
|
|
3
|
+
type Vec3 = [number, number, number];
|
|
4
|
+
type Euler3 = [number, number, number];
|
|
5
|
+
type ColorValue = number | `#${string}`;
|
|
6
|
+
type WallName = "north" | "south" | "east" | "west";
|
|
7
|
+
type MaterialSide = "front" | "back" | "double";
|
|
8
|
+
type TextureRef = string;
|
|
9
|
+
interface Transform {
|
|
10
|
+
position?: Vec3;
|
|
11
|
+
rotation?: Euler3;
|
|
12
|
+
scale?: Vec3;
|
|
13
|
+
}
|
|
14
|
+
interface ShapePath {
|
|
15
|
+
outline: Vec2[];
|
|
16
|
+
holes?: Vec2[][];
|
|
17
|
+
}
|
|
18
|
+
interface CustomGeometrySpec {
|
|
19
|
+
type: "custom";
|
|
20
|
+
positions: number[];
|
|
21
|
+
indices?: number[];
|
|
22
|
+
normals?: number[];
|
|
23
|
+
uvs?: number[];
|
|
24
|
+
computeNormals?: boolean;
|
|
25
|
+
}
|
|
26
|
+
type GeometrySpec = {
|
|
27
|
+
type: "box";
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
depth: number;
|
|
31
|
+
} | {
|
|
32
|
+
type: "rounded-box";
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
depth: number;
|
|
36
|
+
radius?: number;
|
|
37
|
+
segments?: number;
|
|
38
|
+
} | {
|
|
39
|
+
type: "capsule";
|
|
40
|
+
radius: number;
|
|
41
|
+
length: number;
|
|
42
|
+
capSegments?: number;
|
|
43
|
+
radialSegments?: number;
|
|
44
|
+
} | {
|
|
45
|
+
type: "circle";
|
|
46
|
+
radius: number;
|
|
47
|
+
segments?: number;
|
|
48
|
+
thetaStart?: number;
|
|
49
|
+
thetaLength?: number;
|
|
50
|
+
} | {
|
|
51
|
+
type: "plane";
|
|
52
|
+
width: number;
|
|
53
|
+
height: number;
|
|
54
|
+
widthSegments?: number;
|
|
55
|
+
heightSegments?: number;
|
|
56
|
+
} | {
|
|
57
|
+
type: "sphere";
|
|
58
|
+
radius: number;
|
|
59
|
+
widthSegments?: number;
|
|
60
|
+
heightSegments?: number;
|
|
61
|
+
phiStart?: number;
|
|
62
|
+
phiLength?: number;
|
|
63
|
+
thetaStart?: number;
|
|
64
|
+
thetaLength?: number;
|
|
65
|
+
} | {
|
|
66
|
+
type: "cylinder";
|
|
67
|
+
radiusTop: number;
|
|
68
|
+
radiusBottom: number;
|
|
69
|
+
height: number;
|
|
70
|
+
radialSegments?: number;
|
|
71
|
+
heightSegments?: number;
|
|
72
|
+
openEnded?: boolean;
|
|
73
|
+
} | {
|
|
74
|
+
type: "cone";
|
|
75
|
+
radius: number;
|
|
76
|
+
height: number;
|
|
77
|
+
radialSegments?: number;
|
|
78
|
+
heightSegments?: number;
|
|
79
|
+
openEnded?: boolean;
|
|
80
|
+
} | {
|
|
81
|
+
type: "ring";
|
|
82
|
+
innerRadius: number;
|
|
83
|
+
outerRadius: number;
|
|
84
|
+
thetaSegments?: number;
|
|
85
|
+
phiSegments?: number;
|
|
86
|
+
thetaStart?: number;
|
|
87
|
+
thetaLength?: number;
|
|
88
|
+
} | {
|
|
89
|
+
type: "torus";
|
|
90
|
+
radius: number;
|
|
91
|
+
tube: number;
|
|
92
|
+
radialSegments?: number;
|
|
93
|
+
tubularSegments?: number;
|
|
94
|
+
arc?: number;
|
|
95
|
+
} | {
|
|
96
|
+
type: "torus-knot";
|
|
97
|
+
radius: number;
|
|
98
|
+
tube: number;
|
|
99
|
+
tubularSegments?: number;
|
|
100
|
+
radialSegments?: number;
|
|
101
|
+
p?: number;
|
|
102
|
+
q?: number;
|
|
103
|
+
} | {
|
|
104
|
+
type: "tetrahedron";
|
|
105
|
+
radius?: number;
|
|
106
|
+
detail?: number;
|
|
107
|
+
} | {
|
|
108
|
+
type: "octahedron";
|
|
109
|
+
radius?: number;
|
|
110
|
+
detail?: number;
|
|
111
|
+
} | {
|
|
112
|
+
type: "icosahedron";
|
|
113
|
+
radius?: number;
|
|
114
|
+
detail?: number;
|
|
115
|
+
} | {
|
|
116
|
+
type: "dodecahedron";
|
|
117
|
+
radius?: number;
|
|
118
|
+
detail?: number;
|
|
119
|
+
} | {
|
|
120
|
+
type: "polyhedron";
|
|
121
|
+
vertices: number[];
|
|
122
|
+
indices: number[];
|
|
123
|
+
radius?: number;
|
|
124
|
+
detail?: number;
|
|
125
|
+
} | {
|
|
126
|
+
type: "shape";
|
|
127
|
+
path: ShapePath;
|
|
128
|
+
depth?: number;
|
|
129
|
+
} | {
|
|
130
|
+
type: "extrude";
|
|
131
|
+
path: ShapePath;
|
|
132
|
+
depth: number;
|
|
133
|
+
steps?: number;
|
|
134
|
+
bevelEnabled?: boolean;
|
|
135
|
+
bevelThickness?: number;
|
|
136
|
+
bevelSize?: number;
|
|
137
|
+
bevelOffset?: number;
|
|
138
|
+
bevelSegments?: number;
|
|
139
|
+
} | {
|
|
140
|
+
type: "lathe";
|
|
141
|
+
points: Vec2[];
|
|
142
|
+
segments?: number;
|
|
143
|
+
phiStart?: number;
|
|
144
|
+
phiLength?: number;
|
|
145
|
+
} | {
|
|
146
|
+
type: "tube";
|
|
147
|
+
points: Vec3[];
|
|
148
|
+
tubularSegments?: number;
|
|
149
|
+
radius?: number;
|
|
150
|
+
radialSegments?: number;
|
|
151
|
+
closed?: boolean;
|
|
152
|
+
} | CustomGeometrySpec;
|
|
153
|
+
interface StandardMaterialSpec {
|
|
154
|
+
type: "standard";
|
|
155
|
+
color: ColorValue;
|
|
156
|
+
roughness?: number;
|
|
157
|
+
metalness?: number;
|
|
158
|
+
emissive?: ColorValue;
|
|
159
|
+
emissiveIntensity?: number;
|
|
160
|
+
transparent?: boolean;
|
|
161
|
+
opacity?: number;
|
|
162
|
+
side?: MaterialSide;
|
|
163
|
+
map?: TextureRef;
|
|
164
|
+
}
|
|
165
|
+
interface UnlitMaterialSpec {
|
|
166
|
+
type: "unlit";
|
|
167
|
+
color: ColorValue;
|
|
168
|
+
transparent?: boolean;
|
|
169
|
+
opacity?: number;
|
|
170
|
+
side?: MaterialSide;
|
|
171
|
+
map?: TextureRef;
|
|
172
|
+
}
|
|
173
|
+
interface GlassMaterialSpec {
|
|
174
|
+
type: "glass";
|
|
175
|
+
color: ColorValue;
|
|
176
|
+
roughness?: number;
|
|
177
|
+
opacity?: number;
|
|
178
|
+
transmission?: number;
|
|
179
|
+
side?: MaterialSide;
|
|
180
|
+
}
|
|
181
|
+
type MaterialSpec = StandardMaterialSpec | UnlitMaterialSpec | GlassMaterialSpec;
|
|
182
|
+
interface MeshNode extends Transform {
|
|
183
|
+
kind: "mesh";
|
|
184
|
+
geometry: GeometrySpec;
|
|
185
|
+
material: MaterialSpec;
|
|
186
|
+
}
|
|
187
|
+
interface ItemRefNode extends Transform {
|
|
188
|
+
kind: "item";
|
|
189
|
+
itemId: string;
|
|
190
|
+
}
|
|
191
|
+
interface PointLightNode extends Transform {
|
|
192
|
+
kind: "point-light";
|
|
193
|
+
color: ColorValue;
|
|
194
|
+
intensity: number;
|
|
195
|
+
distance?: number;
|
|
196
|
+
decay?: number;
|
|
197
|
+
}
|
|
198
|
+
interface AmbientLightSpec {
|
|
199
|
+
kind: "ambient-light";
|
|
200
|
+
color: ColorValue;
|
|
201
|
+
intensity: number;
|
|
202
|
+
}
|
|
203
|
+
interface DirectionalLightSpec {
|
|
204
|
+
kind: "directional-light";
|
|
205
|
+
color: ColorValue;
|
|
206
|
+
intensity: number;
|
|
207
|
+
position: Vec3;
|
|
208
|
+
target?: Vec3;
|
|
209
|
+
}
|
|
210
|
+
interface HemisphereLightSpec {
|
|
211
|
+
kind: "hemisphere-light";
|
|
212
|
+
skyColor: ColorValue;
|
|
213
|
+
groundColor: ColorValue;
|
|
214
|
+
intensity: number;
|
|
215
|
+
}
|
|
216
|
+
type ItemNode = MeshNode | ItemRefNode | PointLightNode;
|
|
217
|
+
type RoomLight = AmbientLightSpec | DirectionalLightSpec | HemisphereLightSpec;
|
|
218
|
+
interface ItemDefinition {
|
|
219
|
+
id: string;
|
|
220
|
+
collides?: boolean;
|
|
221
|
+
nodes: ItemNode[];
|
|
222
|
+
}
|
|
223
|
+
interface FloorPlacement {
|
|
224
|
+
mode: "floor";
|
|
225
|
+
x: number;
|
|
226
|
+
z: number;
|
|
227
|
+
rotationY?: number;
|
|
228
|
+
lift?: number;
|
|
229
|
+
scale?: Vec3;
|
|
230
|
+
}
|
|
231
|
+
interface WallPlacement {
|
|
232
|
+
mode: "wall";
|
|
233
|
+
wall: WallName;
|
|
234
|
+
offset: number;
|
|
235
|
+
bottom: number;
|
|
236
|
+
rotationY?: number;
|
|
237
|
+
inset?: number;
|
|
238
|
+
scale?: Vec3;
|
|
239
|
+
}
|
|
240
|
+
interface FreePlacement {
|
|
241
|
+
mode: "free";
|
|
242
|
+
position: Vec3;
|
|
243
|
+
rotation?: Euler3;
|
|
244
|
+
scale?: Vec3;
|
|
245
|
+
}
|
|
246
|
+
type Placement = FloorPlacement | WallPlacement | FreePlacement;
|
|
247
|
+
interface RoomInstance {
|
|
248
|
+
id: string;
|
|
249
|
+
itemId: string;
|
|
250
|
+
placement: Placement;
|
|
251
|
+
}
|
|
252
|
+
interface CameraView {
|
|
253
|
+
position: Vec3;
|
|
254
|
+
target: Vec3;
|
|
255
|
+
fov?: number;
|
|
256
|
+
}
|
|
257
|
+
interface RoomShell {
|
|
258
|
+
width: number;
|
|
259
|
+
depth: number;
|
|
260
|
+
height: number;
|
|
261
|
+
openWalls?: WallName[];
|
|
262
|
+
floorMaterial: MaterialSpec;
|
|
263
|
+
wallMaterial: MaterialSpec;
|
|
264
|
+
ceilingMaterial: MaterialSpec;
|
|
265
|
+
ceilingGrid?: {
|
|
266
|
+
spacing: number;
|
|
267
|
+
thickness?: number;
|
|
268
|
+
material: MaterialSpec;
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
interface Atmosphere {
|
|
272
|
+
background: ColorValue;
|
|
273
|
+
fogColor?: ColorValue;
|
|
274
|
+
fogDensity?: number;
|
|
275
|
+
}
|
|
276
|
+
interface RoomDefinition {
|
|
277
|
+
formatVersion: typeof ROOM_FORMAT_VERSION;
|
|
278
|
+
id: string;
|
|
279
|
+
shell: RoomShell;
|
|
280
|
+
atmosphere?: Atmosphere;
|
|
281
|
+
items: Record<string, ItemDefinition>;
|
|
282
|
+
instances: RoomInstance[];
|
|
283
|
+
lights?: RoomLight[];
|
|
284
|
+
cameras: Record<string, CameraView>;
|
|
285
|
+
defaultCamera: string;
|
|
286
|
+
}
|
|
287
|
+
interface ValidationOptions {
|
|
288
|
+
maxItems?: number;
|
|
289
|
+
maxInstances?: number;
|
|
290
|
+
maxLights?: number;
|
|
291
|
+
maxShellExtent?: number;
|
|
292
|
+
}
|
|
293
|
+
declare function validateRoomDefinition(room: RoomDefinition, overrides?: ValidationOptions): RoomDefinition;
|
|
294
|
+
declare const geometry: {
|
|
295
|
+
box: (width: number, height: number, depth: number) => GeometrySpec;
|
|
296
|
+
roundedBox: (width: number, height: number, depth: number, radius?: number, segments?: number) => GeometrySpec;
|
|
297
|
+
capsule: (radius: number, length: number, capSegments?: number, radialSegments?: number) => GeometrySpec;
|
|
298
|
+
circle: (radius: number, segments?: number) => GeometrySpec;
|
|
299
|
+
plane: (width: number, height: number, widthSegments?: number, heightSegments?: number) => GeometrySpec;
|
|
300
|
+
sphere: (radius: number, widthSegments?: number, heightSegments?: number) => GeometrySpec;
|
|
301
|
+
cylinder: (radiusTop: number, radiusBottom: number, height: number, radialSegments?: number, heightSegments?: number, openEnded?: boolean) => GeometrySpec;
|
|
302
|
+
cone: (radius: number, height: number, radialSegments?: number, heightSegments?: number, openEnded?: boolean) => GeometrySpec;
|
|
303
|
+
ring: (innerRadius: number, outerRadius: number, thetaSegments?: number, phiSegments?: number) => GeometrySpec;
|
|
304
|
+
torus: (radius: number, tube: number, radialSegments?: number, tubularSegments?: number, arc?: number) => GeometrySpec;
|
|
305
|
+
torusKnot: (radius: number, tube: number, tubularSegments?: number, radialSegments?: number, p?: number, q?: number) => GeometrySpec;
|
|
306
|
+
tetrahedron: (radius?: number, detail?: number) => GeometrySpec;
|
|
307
|
+
octahedron: (radius?: number, detail?: number) => GeometrySpec;
|
|
308
|
+
icosahedron: (radius?: number, detail?: number) => GeometrySpec;
|
|
309
|
+
dodecahedron: (radius?: number, detail?: number) => GeometrySpec;
|
|
310
|
+
polyhedron: (vertices: number[], indices: number[], radius?: number, detail?: number) => GeometrySpec;
|
|
311
|
+
shape: (path: ShapePath, depth?: number) => GeometrySpec;
|
|
312
|
+
extrude: (path: ShapePath, depth: number, bevelEnabled?: boolean) => GeometrySpec;
|
|
313
|
+
lathe: (points: Vec2[], segments?: number) => GeometrySpec;
|
|
314
|
+
tube: (points: Vec3[], tubularSegments?: number, radius?: number, radialSegments?: number, closed?: boolean) => GeometrySpec;
|
|
315
|
+
custom: (positions: number[], options?: {
|
|
316
|
+
indices?: number[];
|
|
317
|
+
normals?: number[];
|
|
318
|
+
uvs?: number[];
|
|
319
|
+
computeNormals?: boolean;
|
|
320
|
+
}) => GeometrySpec;
|
|
321
|
+
triangles: (triangles: [Vec3, Vec3, Vec3][], options?: {
|
|
322
|
+
normals?: number[];
|
|
323
|
+
uvs?: number[];
|
|
324
|
+
computeNormals?: boolean;
|
|
325
|
+
}) => GeometrySpec;
|
|
326
|
+
};
|
|
327
|
+
declare const material: {
|
|
328
|
+
standard: (options: Omit<StandardMaterialSpec, "type">) => StandardMaterialSpec;
|
|
329
|
+
unlit: (options: Omit<UnlitMaterialSpec, "type">) => UnlitMaterialSpec;
|
|
330
|
+
glass: (options: Omit<GlassMaterialSpec, "type">) => GlassMaterialSpec;
|
|
331
|
+
};
|
|
332
|
+
declare function mesh(geometrySpec: GeometrySpec, materialSpec: MaterialSpec, transform?: Transform): MeshNode;
|
|
333
|
+
declare function item(itemId: string, transform?: Transform): ItemRefNode;
|
|
334
|
+
declare function pointLight(color: ColorValue, intensity: number, distance?: number, decay?: number, transform?: Transform): PointLightNode;
|
|
335
|
+
declare function ambientLight(color: ColorValue, intensity: number): AmbientLightSpec;
|
|
336
|
+
declare function directionalLight(color: ColorValue, intensity: number, position: Vec3, target?: Vec3): DirectionalLightSpec;
|
|
337
|
+
declare function hemisphereLight(skyColor: ColorValue, groundColor: ColorValue, intensity: number): HemisphereLightSpec;
|
|
338
|
+
declare function defineItem(definition: ItemDefinition): ItemDefinition;
|
|
339
|
+
declare function defineRoom(definition: Omit<RoomDefinition, "formatVersion">): RoomDefinition;
|
|
340
|
+
declare function placeFloor(id: string, itemId: string, x: number, z: number, options?: Omit<FloorPlacement, "mode" | "x" | "z">): RoomInstance;
|
|
341
|
+
declare function placeWall(id: string, itemId: string, wall: WallName, offset: number, bottom: number, options?: Omit<WallPlacement, "mode" | "wall" | "offset" | "bottom">): RoomInstance;
|
|
342
|
+
declare function place(id: string, itemId: string, position: Vec3, options?: Omit<FreePlacement, "mode" | "position">): RoomInstance;
|
|
343
|
+
|
|
344
|
+
export { type AmbientLightSpec, type Atmosphere, type CameraView, type ColorValue, type CustomGeometrySpec, type DirectionalLightSpec, type Euler3, type FloorPlacement, type FreePlacement, type GeometrySpec, type GlassMaterialSpec, type HemisphereLightSpec, type ItemDefinition, type ItemNode, type ItemRefNode, type MaterialSide, type MaterialSpec, type MeshNode, type Placement, type PointLightNode, ROOM_FORMAT_VERSION, type RoomDefinition, type RoomInstance, type RoomLight, type RoomShell, type ShapePath, type StandardMaterialSpec, type TextureRef, type Transform, type UnlitMaterialSpec, type ValidationOptions, type Vec2, type Vec3, type WallName, type WallPlacement, ambientLight, defineItem, defineRoom, directionalLight, geometry, hemisphereLight, item, material, mesh, place, placeFloor, placeWall, pointLight, validateRoomDefinition };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var ROOM_FORMAT_VERSION = 1;
|
|
3
|
+
var defaultValidationOptions = {
|
|
4
|
+
maxItems: 128,
|
|
5
|
+
maxInstances: 160,
|
|
6
|
+
maxLights: 24,
|
|
7
|
+
maxShellExtent: 64
|
|
8
|
+
};
|
|
9
|
+
function assertFiniteNumber(value, label) {
|
|
10
|
+
if (!Number.isFinite(value)) {
|
|
11
|
+
throw new Error(`${label} must be a finite number`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function assertVec3(value, label) {
|
|
15
|
+
if (!value) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
for (const [index, part] of value.entries()) {
|
|
19
|
+
assertFiniteNumber(part, `${label}[${index}]`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function assertEuler(value, label) {
|
|
23
|
+
assertVec3(value, label);
|
|
24
|
+
}
|
|
25
|
+
function assertTransform(value, label) {
|
|
26
|
+
assertVec3(value.position, `${label}.position`);
|
|
27
|
+
assertEuler(value.rotation, `${label}.rotation`);
|
|
28
|
+
assertVec3(value.scale, `${label}.scale`);
|
|
29
|
+
}
|
|
30
|
+
function visitItem(itemId, items, visiting, visited) {
|
|
31
|
+
if (visited.has(itemId)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (visiting.has(itemId)) {
|
|
35
|
+
throw new Error(`Recursive item definition detected at "${itemId}"`);
|
|
36
|
+
}
|
|
37
|
+
const item2 = items[itemId];
|
|
38
|
+
if (!item2) {
|
|
39
|
+
throw new Error(`Missing item definition "${itemId}"`);
|
|
40
|
+
}
|
|
41
|
+
visiting.add(itemId);
|
|
42
|
+
for (const node of item2.nodes) {
|
|
43
|
+
if (node.kind === "item") {
|
|
44
|
+
visitItem(node.itemId, items, visiting, visited);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
visiting.delete(itemId);
|
|
48
|
+
visited.add(itemId);
|
|
49
|
+
}
|
|
50
|
+
function validateGeometry(spec, label) {
|
|
51
|
+
switch (spec.type) {
|
|
52
|
+
case "box":
|
|
53
|
+
case "rounded-box":
|
|
54
|
+
assertFiniteNumber(spec.width, `${label}.width`);
|
|
55
|
+
assertFiniteNumber(spec.height, `${label}.height`);
|
|
56
|
+
assertFiniteNumber(spec.depth, `${label}.depth`);
|
|
57
|
+
break;
|
|
58
|
+
case "capsule":
|
|
59
|
+
assertFiniteNumber(spec.radius, `${label}.radius`);
|
|
60
|
+
assertFiniteNumber(spec.length, `${label}.length`);
|
|
61
|
+
break;
|
|
62
|
+
case "circle":
|
|
63
|
+
assertFiniteNumber(spec.radius, `${label}.radius`);
|
|
64
|
+
break;
|
|
65
|
+
case "plane":
|
|
66
|
+
assertFiniteNumber(spec.width, `${label}.width`);
|
|
67
|
+
assertFiniteNumber(spec.height, `${label}.height`);
|
|
68
|
+
break;
|
|
69
|
+
case "sphere":
|
|
70
|
+
assertFiniteNumber(spec.radius, `${label}.radius`);
|
|
71
|
+
break;
|
|
72
|
+
case "cylinder":
|
|
73
|
+
assertFiniteNumber(spec.radiusTop, `${label}.radiusTop`);
|
|
74
|
+
assertFiniteNumber(spec.radiusBottom, `${label}.radiusBottom`);
|
|
75
|
+
assertFiniteNumber(spec.height, `${label}.height`);
|
|
76
|
+
break;
|
|
77
|
+
case "cone":
|
|
78
|
+
assertFiniteNumber(spec.radius, `${label}.radius`);
|
|
79
|
+
assertFiniteNumber(spec.height, `${label}.height`);
|
|
80
|
+
break;
|
|
81
|
+
case "ring":
|
|
82
|
+
assertFiniteNumber(spec.innerRadius, `${label}.innerRadius`);
|
|
83
|
+
assertFiniteNumber(spec.outerRadius, `${label}.outerRadius`);
|
|
84
|
+
break;
|
|
85
|
+
case "torus":
|
|
86
|
+
case "torus-knot":
|
|
87
|
+
assertFiniteNumber(spec.radius, `${label}.radius`);
|
|
88
|
+
assertFiniteNumber(spec.tube, `${label}.tube`);
|
|
89
|
+
break;
|
|
90
|
+
case "tetrahedron":
|
|
91
|
+
case "octahedron":
|
|
92
|
+
case "icosahedron":
|
|
93
|
+
case "dodecahedron":
|
|
94
|
+
if (spec.radius !== void 0) {
|
|
95
|
+
assertFiniteNumber(spec.radius, `${label}.radius`);
|
|
96
|
+
}
|
|
97
|
+
break;
|
|
98
|
+
case "polyhedron":
|
|
99
|
+
if (spec.vertices.length % 3 !== 0) {
|
|
100
|
+
throw new Error(`${label}.vertices must be a multiple of 3`);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
case "shape":
|
|
104
|
+
if (spec.path.outline.length < 3) {
|
|
105
|
+
throw new Error(`${label}.path.outline must have at least 3 points`);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
case "extrude":
|
|
109
|
+
if (spec.path.outline.length < 3) {
|
|
110
|
+
throw new Error(`${label}.path.outline must have at least 3 points`);
|
|
111
|
+
}
|
|
112
|
+
assertFiniteNumber(spec.depth, `${label}.depth`);
|
|
113
|
+
break;
|
|
114
|
+
case "lathe":
|
|
115
|
+
if (spec.points.length < 2) {
|
|
116
|
+
throw new Error(`${label}.points must have at least 2 points`);
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
case "tube":
|
|
120
|
+
if (spec.points.length < 2) {
|
|
121
|
+
throw new Error(`${label}.points must have at least 2 points`);
|
|
122
|
+
}
|
|
123
|
+
break;
|
|
124
|
+
case "custom":
|
|
125
|
+
if (spec.positions.length === 0 || spec.positions.length % 3 !== 0) {
|
|
126
|
+
throw new Error(`${label}.positions must be a non-empty multiple of 3`);
|
|
127
|
+
}
|
|
128
|
+
if (spec.indices && spec.indices.length % 3 !== 0) {
|
|
129
|
+
throw new Error(`${label}.indices must be a multiple of 3`);
|
|
130
|
+
}
|
|
131
|
+
if (spec.normals && spec.normals.length !== spec.positions.length) {
|
|
132
|
+
throw new Error(`${label}.normals must match positions length`);
|
|
133
|
+
}
|
|
134
|
+
if (spec.uvs && spec.uvs.length % 2 !== 0) {
|
|
135
|
+
throw new Error(`${label}.uvs must be a multiple of 2`);
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
default: {
|
|
139
|
+
const exhaustiveCheck = spec;
|
|
140
|
+
return exhaustiveCheck;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function validateRoomDefinition(room, overrides = {}) {
|
|
145
|
+
const options = { ...defaultValidationOptions, ...overrides };
|
|
146
|
+
if (room.formatVersion !== ROOM_FORMAT_VERSION) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`Unsupported room format version "${room.formatVersion}". Expected ${ROOM_FORMAT_VERSION}.`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (!room.id) {
|
|
152
|
+
throw new Error("Room id is required");
|
|
153
|
+
}
|
|
154
|
+
assertFiniteNumber(room.shell.width, "room.shell.width");
|
|
155
|
+
assertFiniteNumber(room.shell.depth, "room.shell.depth");
|
|
156
|
+
assertFiniteNumber(room.shell.height, "room.shell.height");
|
|
157
|
+
if (room.shell.width <= 0 || room.shell.depth <= 0 || room.shell.height <= 0 || room.shell.width > options.maxShellExtent || room.shell.depth > options.maxShellExtent || room.shell.height > options.maxShellExtent) {
|
|
158
|
+
throw new Error("Room shell dimensions are outside allowed bounds");
|
|
159
|
+
}
|
|
160
|
+
const itemEntries = Object.entries(room.items);
|
|
161
|
+
if (itemEntries.length > options.maxItems) {
|
|
162
|
+
throw new Error(`Room has too many item definitions (${itemEntries.length})`);
|
|
163
|
+
}
|
|
164
|
+
if (room.instances.length > options.maxInstances) {
|
|
165
|
+
throw new Error(`Room has too many instances (${room.instances.length})`);
|
|
166
|
+
}
|
|
167
|
+
if ((room.lights?.length ?? 0) > options.maxLights) {
|
|
168
|
+
throw new Error(`Room has too many room lights (${room.lights?.length ?? 0})`);
|
|
169
|
+
}
|
|
170
|
+
if (!room.cameras[room.defaultCamera]) {
|
|
171
|
+
throw new Error(`Default camera "${room.defaultCamera}" does not exist`);
|
|
172
|
+
}
|
|
173
|
+
for (const [itemId, item2] of itemEntries) {
|
|
174
|
+
if (item2.id !== itemId) {
|
|
175
|
+
throw new Error(`Item key "${itemId}" must match item.id "${item2.id}"`);
|
|
176
|
+
}
|
|
177
|
+
for (const [index, node] of item2.nodes.entries()) {
|
|
178
|
+
assertTransform(node, `room.items.${itemId}.nodes[${index}]`);
|
|
179
|
+
if (node.kind === "mesh") {
|
|
180
|
+
validateGeometry(node.geometry, `room.items.${itemId}.nodes[${index}].geometry`);
|
|
181
|
+
}
|
|
182
|
+
if (node.kind === "item" && !room.items[node.itemId]) {
|
|
183
|
+
throw new Error(`Item "${itemId}" references missing child item "${node.itemId}"`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const instance of room.instances) {
|
|
188
|
+
if (!room.items[instance.itemId]) {
|
|
189
|
+
throw new Error(`Instance "${instance.id}" references missing item "${instance.itemId}"`);
|
|
190
|
+
}
|
|
191
|
+
if (instance.placement.mode === "free") {
|
|
192
|
+
assertVec3(instance.placement.position, `room.instances.${instance.id}.placement.position`);
|
|
193
|
+
assertEuler(instance.placement.rotation, `room.instances.${instance.id}.placement.rotation`);
|
|
194
|
+
assertVec3(instance.placement.scale, `room.instances.${instance.id}.placement.scale`);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (instance.placement.mode === "floor") {
|
|
198
|
+
assertFiniteNumber(instance.placement.x, `room.instances.${instance.id}.placement.x`);
|
|
199
|
+
assertFiniteNumber(instance.placement.z, `room.instances.${instance.id}.placement.z`);
|
|
200
|
+
if (instance.placement.rotationY !== void 0) {
|
|
201
|
+
assertFiniteNumber(
|
|
202
|
+
instance.placement.rotationY,
|
|
203
|
+
`room.instances.${instance.id}.placement.rotationY`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
assertVec3(instance.placement.scale, `room.instances.${instance.id}.placement.scale`);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
assertFiniteNumber(instance.placement.offset, `room.instances.${instance.id}.placement.offset`);
|
|
210
|
+
assertFiniteNumber(instance.placement.bottom, `room.instances.${instance.id}.placement.bottom`);
|
|
211
|
+
if (instance.placement.rotationY !== void 0) {
|
|
212
|
+
assertFiniteNumber(
|
|
213
|
+
instance.placement.rotationY,
|
|
214
|
+
`room.instances.${instance.id}.placement.rotationY`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
assertVec3(instance.placement.scale, `room.instances.${instance.id}.placement.scale`);
|
|
218
|
+
}
|
|
219
|
+
for (const camera of Object.values(room.cameras)) {
|
|
220
|
+
assertVec3(camera.position, "room.cameras.position");
|
|
221
|
+
assertVec3(camera.target, "room.cameras.target");
|
|
222
|
+
if (camera.fov !== void 0) {
|
|
223
|
+
assertFiniteNumber(camera.fov, "room.cameras.fov");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const visited = /* @__PURE__ */ new Set();
|
|
227
|
+
for (const itemId of Object.keys(room.items)) {
|
|
228
|
+
visitItem(itemId, room.items, /* @__PURE__ */ new Set(), visited);
|
|
229
|
+
}
|
|
230
|
+
return room;
|
|
231
|
+
}
|
|
232
|
+
var geometry = {
|
|
233
|
+
box: (width, height, depth) => ({
|
|
234
|
+
type: "box",
|
|
235
|
+
width,
|
|
236
|
+
height,
|
|
237
|
+
depth
|
|
238
|
+
}),
|
|
239
|
+
roundedBox: (width, height, depth, radius = 0.03, segments = 4) => ({
|
|
240
|
+
type: "rounded-box",
|
|
241
|
+
width,
|
|
242
|
+
height,
|
|
243
|
+
depth,
|
|
244
|
+
radius,
|
|
245
|
+
segments
|
|
246
|
+
}),
|
|
247
|
+
capsule: (radius, length, capSegments = 8, radialSegments = 12) => ({
|
|
248
|
+
type: "capsule",
|
|
249
|
+
radius,
|
|
250
|
+
length,
|
|
251
|
+
capSegments,
|
|
252
|
+
radialSegments
|
|
253
|
+
}),
|
|
254
|
+
circle: (radius, segments = 32) => ({
|
|
255
|
+
type: "circle",
|
|
256
|
+
radius,
|
|
257
|
+
segments
|
|
258
|
+
}),
|
|
259
|
+
plane: (width, height, widthSegments = 1, heightSegments = 1) => ({
|
|
260
|
+
type: "plane",
|
|
261
|
+
width,
|
|
262
|
+
height,
|
|
263
|
+
widthSegments,
|
|
264
|
+
heightSegments
|
|
265
|
+
}),
|
|
266
|
+
sphere: (radius, widthSegments = 24, heightSegments = 16) => ({
|
|
267
|
+
type: "sphere",
|
|
268
|
+
radius,
|
|
269
|
+
widthSegments,
|
|
270
|
+
heightSegments
|
|
271
|
+
}),
|
|
272
|
+
cylinder: (radiusTop, radiusBottom, height, radialSegments = 16, heightSegments = 1, openEnded = false) => ({
|
|
273
|
+
type: "cylinder",
|
|
274
|
+
radiusTop,
|
|
275
|
+
radiusBottom,
|
|
276
|
+
height,
|
|
277
|
+
radialSegments,
|
|
278
|
+
heightSegments,
|
|
279
|
+
openEnded
|
|
280
|
+
}),
|
|
281
|
+
cone: (radius, height, radialSegments = 16, heightSegments = 1, openEnded = false) => ({
|
|
282
|
+
type: "cone",
|
|
283
|
+
radius,
|
|
284
|
+
height,
|
|
285
|
+
radialSegments,
|
|
286
|
+
heightSegments,
|
|
287
|
+
openEnded
|
|
288
|
+
}),
|
|
289
|
+
ring: (innerRadius, outerRadius, thetaSegments = 24, phiSegments = 1) => ({
|
|
290
|
+
type: "ring",
|
|
291
|
+
innerRadius,
|
|
292
|
+
outerRadius,
|
|
293
|
+
thetaSegments,
|
|
294
|
+
phiSegments
|
|
295
|
+
}),
|
|
296
|
+
torus: (radius, tube, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2) => ({
|
|
297
|
+
type: "torus",
|
|
298
|
+
radius,
|
|
299
|
+
tube,
|
|
300
|
+
radialSegments,
|
|
301
|
+
tubularSegments,
|
|
302
|
+
arc
|
|
303
|
+
}),
|
|
304
|
+
torusKnot: (radius, tube, tubularSegments = 64, radialSegments = 8, p = 2, q = 3) => ({
|
|
305
|
+
type: "torus-knot",
|
|
306
|
+
radius,
|
|
307
|
+
tube,
|
|
308
|
+
tubularSegments,
|
|
309
|
+
radialSegments,
|
|
310
|
+
p,
|
|
311
|
+
q
|
|
312
|
+
}),
|
|
313
|
+
tetrahedron: (radius = 1, detail = 0) => ({ type: "tetrahedron", radius, detail }),
|
|
314
|
+
octahedron: (radius = 1, detail = 0) => ({ type: "octahedron", radius, detail }),
|
|
315
|
+
icosahedron: (radius = 1, detail = 0) => ({ type: "icosahedron", radius, detail }),
|
|
316
|
+
dodecahedron: (radius = 1, detail = 0) => ({ type: "dodecahedron", radius, detail }),
|
|
317
|
+
polyhedron: (vertices, indices, radius = 1, detail = 0) => ({
|
|
318
|
+
type: "polyhedron",
|
|
319
|
+
vertices,
|
|
320
|
+
indices,
|
|
321
|
+
radius,
|
|
322
|
+
detail
|
|
323
|
+
}),
|
|
324
|
+
shape: (path, depth = 0) => ({ type: "shape", path, depth }),
|
|
325
|
+
extrude: (path, depth, bevelEnabled = false) => ({
|
|
326
|
+
type: "extrude",
|
|
327
|
+
path,
|
|
328
|
+
depth,
|
|
329
|
+
bevelEnabled
|
|
330
|
+
}),
|
|
331
|
+
lathe: (points, segments = 12) => ({ type: "lathe", points, segments }),
|
|
332
|
+
tube: (points, tubularSegments = 32, radius = 0.02, radialSegments = 8, closed = false) => ({
|
|
333
|
+
type: "tube",
|
|
334
|
+
points,
|
|
335
|
+
tubularSegments,
|
|
336
|
+
radius,
|
|
337
|
+
radialSegments,
|
|
338
|
+
closed
|
|
339
|
+
}),
|
|
340
|
+
custom: (positions, options = {}) => ({
|
|
341
|
+
type: "custom",
|
|
342
|
+
positions,
|
|
343
|
+
indices: options.indices,
|
|
344
|
+
normals: options.normals,
|
|
345
|
+
uvs: options.uvs,
|
|
346
|
+
computeNormals: options.computeNormals
|
|
347
|
+
}),
|
|
348
|
+
triangles: (triangles, options = {}) => ({
|
|
349
|
+
type: "custom",
|
|
350
|
+
positions: triangles.flat(2),
|
|
351
|
+
normals: options.normals,
|
|
352
|
+
uvs: options.uvs,
|
|
353
|
+
computeNormals: options.computeNormals ?? true
|
|
354
|
+
})
|
|
355
|
+
};
|
|
356
|
+
var material = {
|
|
357
|
+
standard: (options) => ({
|
|
358
|
+
type: "standard",
|
|
359
|
+
...options
|
|
360
|
+
}),
|
|
361
|
+
unlit: (options) => ({
|
|
362
|
+
type: "unlit",
|
|
363
|
+
...options
|
|
364
|
+
}),
|
|
365
|
+
glass: (options) => ({
|
|
366
|
+
type: "glass",
|
|
367
|
+
...options
|
|
368
|
+
})
|
|
369
|
+
};
|
|
370
|
+
function mesh(geometrySpec, materialSpec, transform = {}) {
|
|
371
|
+
return {
|
|
372
|
+
kind: "mesh",
|
|
373
|
+
geometry: geometrySpec,
|
|
374
|
+
material: materialSpec,
|
|
375
|
+
...transform
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function item(itemId, transform = {}) {
|
|
379
|
+
return {
|
|
380
|
+
kind: "item",
|
|
381
|
+
itemId,
|
|
382
|
+
...transform
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
function pointLight(color, intensity, distance = 0, decay = 2, transform = {}) {
|
|
386
|
+
return {
|
|
387
|
+
kind: "point-light",
|
|
388
|
+
color,
|
|
389
|
+
intensity,
|
|
390
|
+
distance,
|
|
391
|
+
decay,
|
|
392
|
+
...transform
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function ambientLight(color, intensity) {
|
|
396
|
+
return { kind: "ambient-light", color, intensity };
|
|
397
|
+
}
|
|
398
|
+
function directionalLight(color, intensity, position, target) {
|
|
399
|
+
return { kind: "directional-light", color, intensity, position, target };
|
|
400
|
+
}
|
|
401
|
+
function hemisphereLight(skyColor, groundColor, intensity) {
|
|
402
|
+
return { kind: "hemisphere-light", skyColor, groundColor, intensity };
|
|
403
|
+
}
|
|
404
|
+
function defineItem(definition) {
|
|
405
|
+
return definition;
|
|
406
|
+
}
|
|
407
|
+
function defineRoom(definition) {
|
|
408
|
+
return validateRoomDefinition({
|
|
409
|
+
formatVersion: ROOM_FORMAT_VERSION,
|
|
410
|
+
...definition
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
function placeFloor(id, itemId, x, z, options = {}) {
|
|
414
|
+
return {
|
|
415
|
+
id,
|
|
416
|
+
itemId,
|
|
417
|
+
placement: {
|
|
418
|
+
mode: "floor",
|
|
419
|
+
x,
|
|
420
|
+
z,
|
|
421
|
+
...options
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function placeWall(id, itemId, wall, offset, bottom, options = {}) {
|
|
426
|
+
return {
|
|
427
|
+
id,
|
|
428
|
+
itemId,
|
|
429
|
+
placement: {
|
|
430
|
+
mode: "wall",
|
|
431
|
+
wall,
|
|
432
|
+
offset,
|
|
433
|
+
bottom,
|
|
434
|
+
...options
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
function place(id, itemId, position, options = {}) {
|
|
439
|
+
return {
|
|
440
|
+
id,
|
|
441
|
+
itemId,
|
|
442
|
+
placement: {
|
|
443
|
+
mode: "free",
|
|
444
|
+
position,
|
|
445
|
+
...options
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
export {
|
|
450
|
+
ROOM_FORMAT_VERSION,
|
|
451
|
+
ambientLight,
|
|
452
|
+
defineItem,
|
|
453
|
+
defineRoom,
|
|
454
|
+
directionalLight,
|
|
455
|
+
geometry,
|
|
456
|
+
hemisphereLight,
|
|
457
|
+
item,
|
|
458
|
+
material,
|
|
459
|
+
mesh,
|
|
460
|
+
place,
|
|
461
|
+
placeFloor,
|
|
462
|
+
placeWall,
|
|
463
|
+
pointLight,
|
|
464
|
+
validateRoomDefinition
|
|
465
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rooms.sh/sdk",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": false,
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format esm --dts --clean",
|
|
22
|
+
"lint": "eslint",
|
|
23
|
+
"format": "prettier --write \"**/*.{ts,tsx}\"",
|
|
24
|
+
"typecheck": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^25.1.0",
|
|
28
|
+
"tsup": "^8.5.1",
|
|
29
|
+
"typescript": "^5.9.3",
|
|
30
|
+
"vitest": "^3.2.4"
|
|
31
|
+
}
|
|
32
|
+
}
|