@drawcall/charta 0.1.14 → 0.1.15
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/locations.d.ts +1 -9
- package/dist/locations.d.ts.map +1 -1
- package/dist/locations.js +18 -42
- package/dist/place/index.d.ts +8 -3
- package/dist/place/index.d.ts.map +1 -1
- package/dist/place/index.js +66 -111
- package/package.json +1 -1
package/dist/locations.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/locations.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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.
|
|
18
|
+
if (this.worldPositions.has(name)) {
|
|
20
19
|
interpreter.reportError(new ChartaError(`duplicate location "${name}"`, interpreter.getSource(), loc));
|
|
21
20
|
continue;
|
|
22
21
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
43
|
-
if (!
|
|
36
|
+
const pos = this.worldPositions.get(name);
|
|
37
|
+
if (!pos) {
|
|
44
38
|
throw new Error(`Unknown location "${name}".`);
|
|
45
39
|
}
|
|
46
|
-
|
|
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
|
}
|
package/dist/place/index.d.ts
CHANGED
|
@@ -17,17 +17,22 @@ export declare function measureObject(object: Object3D): Vector3;
|
|
|
17
17
|
export declare class PrefabBatchBuilder {
|
|
18
18
|
readonly prefabSize: Vector3;
|
|
19
19
|
private readonly buildFromMatrices;
|
|
20
|
-
|
|
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
|
|
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
38
|
private batchBuilders;
|
|
@@ -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;
|
|
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,OAAO,CAAC,aAAa,CAAgC;gBAEzC,WAAW,EAAE,WAAW,EAAE,iBAAiB,CAAC,EAAE,QAAQ,EAAE,IAAI,GAAE,iBAAsB;IAuPhG,OAAO;CAOR"}
|
package/dist/place/index.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
31
|
-
this.matrices = [];
|
|
32
|
-
return batch;
|
|
30
|
+
return this.buildFromMatrices(this.matrices);
|
|
33
31
|
}
|
|
34
32
|
}
|
|
35
33
|
export class PlaceGroup extends Group {
|
|
36
34
|
batchBuilders = new Set();
|
|
37
35
|
constructor(interpreter, _materialFallback, opts = {}) {
|
|
38
36
|
super();
|
|
39
|
-
|
|
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
|
-
|
|
94
|
-
|
|
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
|
|
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 (
|
|
103
|
-
py =
|
|
69
|
+
if (transformOpts.useFixedY) {
|
|
70
|
+
py = transformOpts.fixedY;
|
|
104
71
|
}
|
|
105
72
|
else {
|
|
106
|
-
py = tilesGeometry.getHeight(cell.row, cell.col,
|
|
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 (
|
|
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(
|
|
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(
|
|
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
|
-
|
|
143
|
-
|
|
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
|
|
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
|
|
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;
|
|
112
|
+
// In mock mode, we only validate asset existence - skip expensive placement computation
|
|
113
|
+
if (mock)
|
|
114
|
+
continue;
|
|
166
115
|
this.batchBuilders.add(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((
|
|
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
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
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.add(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
|
-
|
|
257
|
-
|
|
208
|
+
// Build step: skip if mock or skipBuild is enabled
|
|
209
|
+
if (!mock && !skipBuild) {
|
|
210
|
+
for (const batchBuilder of this.batchBuilders) {
|
|
211
|
+
this.add(batchBuilder.build());
|
|
212
|
+
}
|
|
258
213
|
}
|
|
259
214
|
}
|
|
260
215
|
dispose() {
|