@drawcall/charta 0.1.14 → 0.1.16

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.
@@ -3,16 +3,8 @@ import { Vector3 } from 'three';
3
3
  import { locationSchema } from './schemas.js';
4
4
  export { locationSchema };
5
5
  export declare class Locations {
6
- private readonly interpreter;
7
- private readonly nameToCell;
6
+ readonly worldPositions: Map<string, Vector3>;
8
7
  constructor(interpreter: Interpreter);
9
- /**
10
- * Resolve a named location to a world-space position (x, y, z).
11
- * - X/Z are the center of the owning cell plus any offsetX/offsetZ.
12
- * - Y is sampled from the nearest relevant layer stack at that cell.
13
- *
14
- * If terrain data is unavailable, Y defaults to 0.
15
- */
16
8
  getWorldPosition(name: string, target: Vector3): Vector3;
17
9
  }
18
10
  //# sourceMappingURL=locations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../src/locations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAG9C,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,CAAA;AAIzB,qBAAa,SAAS;IAGR,OAAO,CAAC,QAAQ,CAAC,WAAW;IAFxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;gBAEhC,WAAW,EAAE,WAAW;IA2BrD;;;;;;OAMG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO;CA0BzD"}
1
+ {"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../src/locations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAG9C,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAC/B,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,CAAA;AAEzB,qBAAa,SAAS;IACpB,SAAgB,cAAc,uBAA6B;gBAE/C,WAAW,EAAE,WAAW;IAgCpC,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO;CAOzD"}
package/dist/locations.js CHANGED
@@ -1,66 +1,42 @@
1
1
  import { TilesGeometry } from './tiles/geometry.js';
2
2
  import { ChartaError } from './errors.js';
3
+ import { Vector3 } from 'three';
3
4
  import { locationSchema } from './schemas.js';
4
5
  export { locationSchema };
5
6
  export class Locations {
6
- interpreter;
7
- nameToCell = new Map();
7
+ worldPositions = new Map();
8
8
  constructor(interpreter) {
9
- this.interpreter = interpreter;
10
9
  const rows = interpreter.getRows();
11
10
  const cols = interpreter.getCols();
11
+ const tilesGeometry = interpreter.getAsset(TilesGeometry, 'tilesGeometry');
12
+ const isLayer = (c) => c.name === 'ground' || c.name === 'ceiling';
12
13
  for (let row = 0; row < rows; row++) {
13
14
  for (let col = 0; col < cols; col++) {
14
- const entries = interpreter.getCalls([row, col], {
15
- location: locationSchema,
16
- });
15
+ const entries = interpreter.getCalls([row, col], { location: locationSchema });
17
16
  for (const [, parsed, callIdx, loc] of entries) {
18
17
  const name = parsed.name;
19
- if (this.nameToCell.has(name)) {
18
+ if (this.worldPositions.has(name)) {
20
19
  interpreter.reportError(new ChartaError(`duplicate location "${name}"`, interpreter.getSource(), loc));
21
20
  continue;
22
21
  }
23
- this.nameToCell.set(name, {
24
- row,
25
- col,
26
- callIdx,
27
- offsetX: parsed.offsetX ?? 0,
28
- offsetZ: parsed.offsetZ ?? 0,
29
- });
22
+ const offsetX = parsed.offsetX ?? 0;
23
+ const offsetZ = parsed.offsetZ ?? 0;
24
+ const [cx, cz] = interpreter.getWorldCellCenter(row, col);
25
+ const x = cx + offsetX;
26
+ const z = cz + offsetZ;
27
+ const layersBefore = interpreter.countCalls([row, col], isLayer, 0, callIdx);
28
+ const stackIndex = layersBefore > 0 ? layersBefore - 1 : 0;
29
+ const y = tilesGeometry?.getHeight(row, col, stackIndex, offsetX, offsetZ) ?? 0;
30
+ this.worldPositions.set(name, new Vector3(x, y, z));
30
31
  }
31
32
  }
32
33
  }
33
34
  }
34
- /**
35
- * Resolve a named location to a world-space position (x, y, z).
36
- * - X/Z are the center of the owning cell plus any offsetX/offsetZ.
37
- * - Y is sampled from the nearest relevant layer stack at that cell.
38
- *
39
- * If terrain data is unavailable, Y defaults to 0.
40
- */
41
35
  getWorldPosition(name, target) {
42
- const cellLoc = this.nameToCell.get(name);
43
- if (!cellLoc) {
36
+ const pos = this.worldPositions.get(name);
37
+ if (!pos) {
44
38
  throw new Error(`Unknown location "${name}".`);
45
39
  }
46
- const { row, col, callIdx, offsetX, offsetZ } = cellLoc;
47
- const [cx, cz] = this.interpreter.getWorldCellCenter(row, col);
48
- const x = cx + offsetX;
49
- const z = cz + offsetZ;
50
- // Determine the stack index similarly to how walls/pillars derive defaults.
51
- const isLayer = (c) => c.name === 'ground' || c.name === 'ceiling';
52
- const layersBefore = this.interpreter.countCalls([row, col], isLayer, 0, callIdx);
53
- let stackIndex;
54
- if (layersBefore > 0) {
55
- stackIndex = layersBefore - 1;
56
- }
57
- else {
58
- const layersAfter = this.interpreter.countCalls([row, col], isLayer, callIdx + 1);
59
- stackIndex = layersAfter > 0 ? 0 : 0;
60
- }
61
- const tilesGeometry = this.interpreter.getAsset(TilesGeometry, 'tilesGeometry');
62
- const y = tilesGeometry?.getHeight(row, col, stackIndex, offsetX, offsetZ) ?? 0;
63
- target.set(x, y, z);
64
- return target;
40
+ return target.copy(pos);
65
41
  }
66
42
  }
@@ -17,20 +17,25 @@ export declare function measureObject(object: Object3D): Vector3;
17
17
  export declare class PrefabBatchBuilder {
18
18
  readonly prefabSize: Vector3;
19
19
  private readonly buildFromMatrices;
20
- private matrices;
20
+ readonly matrices: Array<Matrix4>;
21
21
  constructor(prefabSize: Vector3, buildFromMatrices: (matrices: Array<Matrix4>) => Object3D);
22
22
  add(matrix: Matrix4): this;
23
23
  build(): Object3D;
24
24
  }
25
25
  export type PlaceGroupOptions = {
26
26
  /**
27
- * Mock mode: validates scatter/place calls exist but skips expensive instance generation.
28
- * Useful for server-side validation. Default: false.
27
+ * Mock mode: validates scatter/place calls and assets but skips expensive transform
28
+ * computation and instance generation. Useful for server-side validation. Default: false.
29
29
  */
30
30
  mock?: boolean;
31
+ /**
32
+ * Skip the final build() call on batch builders. Computes all placements but doesn't
33
+ * create the final Object3D instances. Useful for testing placement logic. Default: false.
34
+ */
35
+ skipBuild?: boolean;
31
36
  };
32
37
  export declare class PlaceGroup extends Group {
33
- private batchBuilders;
38
+ readonly batchBuilders: Map<string, PrefabBatchBuilder>;
34
39
  constructor(interpreter: Interpreter, _materialFallback?: Material, opts?: PlaceGroupOptions);
35
40
  dispose(): void;
36
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/place/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAQ,MAAM,OAAO,CAAA;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAY/C,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;AAEvD,MAAM,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE;IACvC,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,UAAU,CAAA;IACpB,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,WAAW,CAAA;IACxB,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAA;IAC5D,IAAI,EAAE,MAAM,CAAA;CACb,KAAK,IAAI,CAAA;AAKV,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAOvD;AAED,qBAAa,kBAAkB;aAIX,UAAU,EAAE,OAAO;IACnC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAJpC,OAAO,CAAC,QAAQ,CAAqB;gBAGnB,UAAU,EAAE,OAAO,EAClB,iBAAiB,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,QAAQ;IAG5E,GAAG,CAAC,MAAM,EAAE,OAAO;IAKZ,KAAK,IAAI,QAAQ;CAKzB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAA;AAED,qBAAa,UAAW,SAAQ,KAAK;IACnC,OAAO,CAAC,aAAa,CAAgC;gBAEzC,WAAW,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE,QAAQ,EAAE,IAAI,GAAE,iBAAsB;IAsThG,OAAO;CAOR"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/place/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAQ,MAAM,OAAO,CAAA;AACrF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAO/C,MAAM,MAAM,eAAe,GAAG,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;AAEvD,MAAM,MAAM,mBAAmB,GAAG,CAAC,IAAI,EAAE;IACvC,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,UAAU,CAAA;IACpB,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,WAAW,CAAA;IACxB,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAA;IAC5D,IAAI,EAAE,MAAM,CAAA;CACb,KAAK,IAAI,CAAA;AAKV,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,GAAG,OAAO,CAOvD;AAED,qBAAa,kBAAkB;aAIX,UAAU,EAAE,OAAO;IACnC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAJpC,SAAgB,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAK;gBAG3B,UAAU,EAAE,OAAO,EAClB,iBAAiB,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,QAAQ;IAG5E,GAAG,CAAC,MAAM,EAAE,OAAO;IAKZ,KAAK,IAAI,QAAQ;CAGzB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;IACd;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,qBAAa,UAAW,SAAQ,KAAK;IACnC,SAAgB,aAAa,kCAAwC;gBAEzD,WAAW,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE,QAAQ,EAAE,IAAI,GAAE,iBAAsB;IAuPhG,OAAO;CAOR"}
@@ -2,7 +2,7 @@ import { Group, Matrix4, Quaternion, Vector3, Box3 } from 'three';
2
2
  import { TilesGeometry } from '../tiles/geometry.js';
3
3
  import { createRng, hashNumbersToUint32, hashStringToUint32 } from '../utils/random.js';
4
4
  import { degToRad } from 'three/src/math/MathUtils.js';
5
- import { locationSchema, placeSchema, scatterSchema, isPointInsideFootprint, } from '../schemas.js';
5
+ import { locationSchema, placeSchema, scatterSchema, isPointInsideFootprint } from '../schemas.js';
6
6
  import { ChartaError } from '../errors.js';
7
7
  const boxHelper = new Box3();
8
8
  const matrixHelper = new Matrix4();
@@ -27,145 +27,95 @@ export class PrefabBatchBuilder {
27
27
  return this;
28
28
  }
29
29
  build() {
30
- const batch = this.buildFromMatrices(this.matrices);
31
- this.matrices = [];
32
- return batch;
30
+ return this.buildFromMatrices(this.matrices);
33
31
  }
34
32
  }
35
33
  export class PlaceGroup extends Group {
36
- batchBuilders = new Set();
34
+ batchBuilders = new Map();
37
35
  constructor(interpreter, _materialFallback, opts = {}) {
38
36
  super();
39
- // Mock mode: validate scatter/place calls exist, skip expensive generation
40
- if (opts.mock) {
41
- const rows = interpreter.getRows();
42
- const cols = interpreter.getCols();
43
- const cellSize = interpreter.getCellSize();
44
- for (let row = 0; row < rows; row++) {
45
- for (let col = 0; col < cols; col++) {
46
- // getCalls validates the schema and reports errors
47
- const scatterEntries = interpreter.getCalls([row, col], { scatter: scatterSchema });
48
- const placeEntries = interpreter.getCalls([row, col], { place: placeSchema });
49
- const cx = (col - cols / 2 + 0.5) * cellSize;
50
- const cz = (row - rows / 2 + 0.5) * cellSize;
51
- // Collect location points for overlap validation
52
- const locationEntries = interpreter.getCalls([row, col], { location: locationSchema });
53
- const locationPoints = locationEntries.map(([, parsed]) => ({
54
- x: cx + (parsed.offsetX ?? 0),
55
- z: cz + (parsed.offsetZ ?? 0),
56
- }));
57
- // Validate that referenced models exist (triggers "unknown asset" errors)
58
- for (const [, parsed, , loc] of scatterEntries) {
59
- interpreter.getAsset(PrefabBatchBuilder, `${parsed.model}Prefab`, loc);
60
- }
61
- for (const [, parsed, , loc] of placeEntries) {
62
- const batchBuilder = interpreter.getAsset(PrefabBatchBuilder, `${parsed.model}Prefab`, loc);
63
- if (!batchBuilder)
64
- continue;
65
- // Validate that no location is inside this place footprint
66
- const px = cx + (parsed.offsetX ?? 0);
67
- const pz = cz + (parsed.offsetZ ?? 0);
68
- const usualSize = batchBuilder.prefabSize;
69
- // Compute scale factor
70
- let scaleFactor = 1;
71
- if (parsed.sizeX !== undefined || parsed.sizeZ !== undefined) {
72
- const safeUsualX = Math.max(usualSize.x, 0.001);
73
- const safeUsualZ = Math.max(usualSize.z, 0.001);
74
- const sFactorX = parsed.sizeX !== undefined ? parsed.sizeX / safeUsualX : undefined;
75
- const sFactorZ = parsed.sizeZ !== undefined ? parsed.sizeZ / safeUsualZ : undefined;
76
- scaleFactor = Math.min(sFactorX ?? sFactorZ ?? 1, sFactorZ ?? sFactorX ?? 1);
77
- }
78
- const halfX = (usualSize.x * scaleFactor) / 2;
79
- const halfZ = (usualSize.z * scaleFactor) / 2;
80
- for (const locPoint of locationPoints) {
81
- if (isPointInsideFootprint(locPoint.x, locPoint.z, px, pz, halfX, halfZ)) {
82
- interpreter.reportError(new ChartaError(`location is inside place(${parsed.model}) footprint`, interpreter.getSource(), loc));
83
- }
84
- }
85
- }
86
- }
87
- }
88
- return;
89
- }
37
+ const { mock = false, skipBuild = false } = opts;
90
38
  const rows = interpreter.getRows();
91
39
  const cols = interpreter.getCols();
92
40
  const cellSize = interpreter.getCellSize();
93
- const tilesGeometry = interpreter.getAsset(TilesGeometry, 'tilesGeometry');
94
- if (!tilesGeometry) {
41
+ // Only get tilesGeometry if not in mock mode (needed for height calculations)
42
+ const tilesGeometry = mock ? null : interpreter.getAsset(TilesGeometry, 'tilesGeometry');
43
+ // Return early if not mock and tilesGeometry is missing
44
+ if (!mock && !tilesGeometry) {
95
45
  return;
96
46
  }
97
- const computeTransform = (px, pz, cell, opts) => {
47
+ const computeScale = (usualSize, sizeX, sizeZ) => {
48
+ if (sizeX === undefined && sizeZ === undefined)
49
+ return new Vector3(1, 1, 1);
50
+ // Avoid division by zero
51
+ const safeUsualX = Math.max(usualSize.x, 0.001);
52
+ const safeUsualZ = Math.max(usualSize.z, 0.001);
53
+ let sFactorX;
54
+ let sFactorZ;
55
+ if (sizeX !== undefined) {
56
+ sFactorX = sizeX / safeUsualX;
57
+ }
58
+ if (sizeZ !== undefined) {
59
+ sFactorZ = sizeZ / safeUsualZ;
60
+ }
61
+ const scale = Math.min(sFactorX ?? sFactorZ ?? 1, sFactorZ ?? sFactorX ?? 1);
62
+ return new Vector3(scale, scale, scale);
63
+ };
64
+ const computeTransform = (px, pz, cell, transformOpts) => {
98
65
  const normal = new Vector3();
99
66
  const offsetX = px - cell.center[0];
100
67
  const offsetZ = pz - cell.center[1];
101
68
  let py = 0;
102
- if (opts.useFixedY) {
103
- py = opts.fixedY;
69
+ if (transformOpts.useFixedY) {
70
+ py = transformOpts.fixedY;
104
71
  }
105
72
  else {
106
- py = tilesGeometry.getHeight(cell.row, cell.col, opts.layerIndex, offsetX, offsetZ, normal);
73
+ py = tilesGeometry.getHeight(cell.row, cell.col, transformOpts.layerIndex, offsetX, offsetZ, normal);
107
74
  }
108
75
  const pos = new Vector3(px, py, pz);
109
76
  const quat = new Quaternion();
110
- if (opts.alignMode === 'normal' && !opts.useFixedY) {
77
+ if (transformOpts.alignMode === 'normal' && !transformOpts.useFixedY) {
111
78
  const qAlign = new Quaternion().setFromUnitVectors(new Vector3(0, 1, 0), normal);
112
- const qYaw = new Quaternion().setFromAxisAngle(normal, degToRad(opts.yaw));
79
+ const qYaw = new Quaternion().setFromAxisAngle(normal, degToRad(transformOpts.yaw));
113
80
  quat.multiplyQuaternions(qYaw, qAlign);
114
81
  }
115
82
  else {
116
- quat.setFromAxisAngle(new Vector3(0, 1, 0), degToRad(opts.yaw));
83
+ quat.setFromAxisAngle(new Vector3(0, 1, 0), degToRad(transformOpts.yaw));
117
84
  }
118
85
  // Scale is handled by the factory via size
119
86
  const scl = new Vector3(1, 1, 1);
120
87
  const matrix = new Matrix4().compose(pos, quat, scl);
121
88
  return { pos, quat, scl, matrix };
122
89
  };
123
- const computeScale = (usualSize, sizeX, sizeZ) => {
124
- if (sizeX === undefined && sizeZ === undefined)
125
- return new Vector3(1, 1, 1);
126
- // Avoid division by zero
127
- const safeUsualX = Math.max(usualSize.x, 0.001);
128
- const safeUsualZ = Math.max(usualSize.z, 0.001);
129
- let sFactorX;
130
- let sFactorZ;
131
- if (sizeX !== undefined) {
132
- sFactorX = sizeX / safeUsualX;
133
- }
134
- if (sizeZ !== undefined) {
135
- sFactorZ = sizeZ / safeUsualZ;
136
- }
137
- const scale = Math.min(sFactorX ?? sFactorZ ?? 1, sFactorZ ?? sFactorX ?? 1);
138
- return new Vector3(scale, scale, scale);
139
- };
140
90
  for (let row = 0; row < rows; row++) {
141
91
  for (let col = 0; col < cols; col++) {
142
- const scatterEntries = interpreter.getCalls([row, col], {
143
- scatter: scatterSchema,
144
- });
145
- const placeEntries = interpreter.getCalls([row, col], {
146
- place: placeSchema,
147
- });
92
+ // getCalls validates the schema and reports errors
93
+ const scatterEntries = interpreter.getCalls([row, col], { scatter: scatterSchema });
94
+ const placeEntries = interpreter.getCalls([row, col], { place: placeSchema });
148
95
  if (scatterEntries.length === 0 && placeEntries.length === 0)
149
96
  continue;
150
97
  const cx = (col - cols / 2 + 0.5) * cellSize;
151
98
  const cz = (row - rows / 2 + 0.5) * cellSize;
152
99
  const half = cellSize * 0.5;
153
- // Collect location points in this cell for collision detection
100
+ // Collect location points in this cell for collision/footprint validation
154
101
  const locationEntries = interpreter.getCalls([row, col], { location: locationSchema });
155
102
  const locationPoints = locationEntries.map(([, parsed]) => ({
156
103
  x: cx + (parsed.offsetX ?? 0),
157
104
  z: cz + (parsed.offsetZ ?? 0),
158
105
  }));
159
- // scatter: randomized distribution, density is count for now
106
+ // scatter: randomized distribution
160
107
  for (const [, parsed, callIdx, loc] of scatterEntries) {
161
- const layerIndex = interpreter.countCalls([row, col], (c) => c.name === 'ground' || c.name === 'ceiling', 0, callIdx) - 1;
162
108
  const prefabName = `${parsed.model}Prefab`;
163
109
  const batchBuilder = interpreter.getAsset(PrefabBatchBuilder, prefabName, loc);
164
110
  if (!batchBuilder)
165
111
  continue;
166
- this.batchBuilders.add(batchBuilder);
112
+ // In mock mode, we only validate asset existence - skip expensive placement computation
113
+ if (mock)
114
+ continue;
115
+ this.batchBuilders.set(parsed.model, batchBuilder);
167
116
  const usualSize = new Vector3();
168
117
  usualSize.copy(batchBuilder.prefabSize);
118
+ const layerIndex = interpreter.countCalls([row, col], (c) => c.name === 'ground' || c.name === 'ceiling', 0, callIdx) - 1;
169
119
  const base = hashStringToUint32(parsed.model);
170
120
  const seed = hashNumbersToUint32(base, row, col);
171
121
  const rng = createRng(seed);
@@ -211,7 +161,7 @@ export class PlaceGroup extends Group {
211
161
  // Check if any location point falls inside this scattered object's footprint
212
162
  const halfX = (usualSize.x * scale.x) / 2;
213
163
  const halfZ = (usualSize.z * scale.z) / 2;
214
- const overlapsLocation = locationPoints.some((loc) => isPointInsideFootprint(loc.x, loc.z, px, pz, halfX, halfZ));
164
+ const overlapsLocation = locationPoints.some((locPoint) => isPointInsideFootprint(locPoint.x, locPoint.z, px, pz, halfX, halfZ));
215
165
  if (overlapsLocation)
216
166
  continue;
217
167
  batchBuilder.add(matrixHelper.compose(pos, quat, scl));
@@ -219,42 +169,47 @@ export class PlaceGroup extends Group {
219
169
  }
220
170
  // place: exact in-cell placement
221
171
  for (const [, parsed, callIdx, loc] of placeEntries) {
222
- const layerIndex = interpreter.countCalls([row, col], (c) => c.name === 'ground' || c.name === 'ceiling', 0, callIdx) - 1;
223
- const px = cx + (parsed.offsetX ?? 0);
224
- const pz = cz + (parsed.offsetZ ?? 0);
225
- // In place command, yaw is exact, no min/max range
226
- const yaw = parsed.yaw ?? 0;
227
- const alignMode = (parsed.align ?? 'up').toLowerCase();
228
- const { pos, quat, scl } = computeTransform(px, pz, { row, col, center: [cx, cz] }, {
229
- alignMode,
230
- yaw,
231
- useFixedY: parsed.bottomY != null,
232
- fixedY: parsed.bottomY,
233
- layerIndex,
234
- });
235
172
  const prefabName = `${parsed.model}Prefab`;
236
173
  const batchBuilder = interpreter.getAsset(PrefabBatchBuilder, prefabName, loc);
237
174
  if (!batchBuilder)
238
175
  continue;
239
- this.batchBuilders.add(batchBuilder);
240
- const usualSize = new Vector3();
241
- usualSize.copy(batchBuilder.prefabSize);
176
+ const usualSize = batchBuilder.prefabSize;
177
+ const px = cx + (parsed.offsetX ?? 0);
178
+ const pz = cz + (parsed.offsetZ ?? 0);
179
+ // Compute scale for footprint validation (needed for both mock and normal mode)
242
180
  const scale = computeScale(usualSize, parsed.sizeX, parsed.sizeZ);
243
- scl.multiply(scale);
244
- // Validate that no location point is inside this placed object's footprint
245
181
  const halfX = (usualSize.x * scale.x) / 2;
246
182
  const halfZ = (usualSize.z * scale.z) / 2;
183
+ // Validate that no location point is inside this placed object's footprint
247
184
  for (const locPoint of locationPoints) {
248
185
  if (isPointInsideFootprint(locPoint.x, locPoint.z, px, pz, halfX, halfZ)) {
249
186
  interpreter.reportError(new ChartaError(`location is inside place(${parsed.model}) footprint`, interpreter.getSource(), loc));
250
187
  }
251
188
  }
189
+ // In mock mode, skip expensive transform computation and placement
190
+ if (mock)
191
+ continue;
192
+ this.batchBuilders.set(parsed.model, batchBuilder);
193
+ const layerIndex = interpreter.countCalls([row, col], (c) => c.name === 'ground' || c.name === 'ceiling', 0, callIdx) - 1;
194
+ const yaw = parsed.yaw ?? 0;
195
+ const alignMode = (parsed.align ?? 'up').toLowerCase();
196
+ const { pos, quat, scl } = computeTransform(px, pz, { row, col, center: [cx, cz] }, {
197
+ alignMode,
198
+ yaw,
199
+ useFixedY: parsed.bottomY != null,
200
+ fixedY: parsed.bottomY,
201
+ layerIndex,
202
+ });
203
+ scl.multiply(scale);
252
204
  batchBuilder.add(matrixHelper.compose(pos, quat, scl));
253
205
  }
254
206
  }
255
207
  }
256
- for (const batchBuilder of this.batchBuilders) {
257
- this.add(batchBuilder.build());
208
+ // Build step: skip if mock or skipBuild is enabled
209
+ if (!mock && !skipBuild) {
210
+ for (const batchBuilder of this.batchBuilders.values()) {
211
+ this.add(batchBuilder.build());
212
+ }
258
213
  }
259
214
  }
260
215
  dispose() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drawcall/charta",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "author": "Bela Bohlender",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://drawcall.ai",