@drawcall/charta 0.0.0

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.
Files changed (71) hide show
  1. package/LICENSE +7 -0
  2. package/dist/assets/loader.d.ts +21 -0
  3. package/dist/assets/loader.d.ts.map +1 -0
  4. package/dist/assets/loader.js +113 -0
  5. package/dist/errors.d.ts +11 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/errors.js +27 -0
  8. package/dist/grammar.d.ts +29 -0
  9. package/dist/grammar.d.ts.map +1 -0
  10. package/dist/grammar.js +119 -0
  11. package/dist/grass/index.d.ts +25 -0
  12. package/dist/grass/index.d.ts.map +1 -0
  13. package/dist/grass/index.js +177 -0
  14. package/dist/grass/material.d.ts +10 -0
  15. package/dist/grass/material.d.ts.map +1 -0
  16. package/dist/grass/material.js +80 -0
  17. package/dist/index.d.ts +13 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +12 -0
  20. package/dist/interpreter.d.ts +47 -0
  21. package/dist/interpreter.d.ts.map +1 -0
  22. package/dist/interpreter.js +226 -0
  23. package/dist/locations.d.ts +16 -0
  24. package/dist/locations.d.ts.map +1 -0
  25. package/dist/locations.js +58 -0
  26. package/dist/parser.d.ts +9 -0
  27. package/dist/parser.d.ts.map +1 -0
  28. package/dist/parser.js +47 -0
  29. package/dist/pillars/index.d.ts +7 -0
  30. package/dist/pillars/index.d.ts.map +1 -0
  31. package/dist/pillars/index.js +154 -0
  32. package/dist/pillars/material.d.ts +3 -0
  33. package/dist/pillars/material.d.ts.map +1 -0
  34. package/dist/pillars/material.js +43 -0
  35. package/dist/place/index.d.ts +37 -0
  36. package/dist/place/index.d.ts.map +1 -0
  37. package/dist/place/index.js +216 -0
  38. package/dist/tiles/geometry.d.ts +46 -0
  39. package/dist/tiles/geometry.d.ts.map +1 -0
  40. package/dist/tiles/geometry.js +463 -0
  41. package/dist/tiles/index.d.ts +18 -0
  42. package/dist/tiles/index.d.ts.map +1 -0
  43. package/dist/tiles/index.js +121 -0
  44. package/dist/tiles/material.d.ts +6 -0
  45. package/dist/tiles/material.d.ts.map +1 -0
  46. package/dist/tiles/material.js +88 -0
  47. package/dist/utils/instanced-mesh-group.d.ts +17 -0
  48. package/dist/utils/instanced-mesh-group.d.ts.map +1 -0
  49. package/dist/utils/instanced-mesh-group.js +59 -0
  50. package/dist/utils/random.d.ts +4 -0
  51. package/dist/utils/random.d.ts.map +1 -0
  52. package/dist/utils/random.js +19 -0
  53. package/dist/utils/texture.d.ts +3 -0
  54. package/dist/utils/texture.d.ts.map +1 -0
  55. package/dist/utils/texture.js +30 -0
  56. package/dist/walls/index.d.ts +87 -0
  57. package/dist/walls/index.d.ts.map +1 -0
  58. package/dist/walls/index.js +376 -0
  59. package/dist/walls/material.d.ts +3 -0
  60. package/dist/walls/material.d.ts.map +1 -0
  61. package/dist/walls/material.js +67 -0
  62. package/dist/water/index.d.ts +10 -0
  63. package/dist/water/index.d.ts.map +1 -0
  64. package/dist/water/index.js +46 -0
  65. package/dist/water/material.d.ts +5 -0
  66. package/dist/water/material.d.ts.map +1 -0
  67. package/dist/water/material.js +46 -0
  68. package/dist/water/texture.d.ts +15 -0
  69. package/dist/water/texture.d.ts.map +1 -0
  70. package/dist/water/texture.js +201 -0
  71. package/package.json +39 -0
@@ -0,0 +1,13 @@
1
+ export * from "./parser.js";
2
+ export * from "./interpreter.js";
3
+ export * from "./tiles/index.js";
4
+ export * from "./walls/index.js";
5
+ export * from "./grass/index.js";
6
+ export * from "./place/index.js";
7
+ export * from "./errors.js";
8
+ export * from "./pillars/index.js";
9
+ export * from "./water/index.js";
10
+ export * from "./locations.js";
11
+ export * from "./utils/instanced-mesh-group.js";
12
+ export * from "./assets/loader.js";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iCAAiC,CAAA;AAC/C,cAAc,oBAAoB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export * from "./parser.js";
2
+ export * from "./interpreter.js";
3
+ export * from "./tiles/index.js";
4
+ export * from "./walls/index.js";
5
+ export * from "./grass/index.js";
6
+ export * from "./place/index.js";
7
+ export * from "./errors.js";
8
+ export * from "./pillars/index.js";
9
+ export * from "./water/index.js";
10
+ export * from "./locations.js";
11
+ export * from "./utils/instanced-mesh-group.js";
12
+ export * from "./assets/loader.js";
@@ -0,0 +1,47 @@
1
+ import { coerce, output, ZodObject } from 'zod';
2
+ import { Call } from './parser.js';
3
+ import { ChartaError, ErrorLocation } from './errors.js';
4
+ export declare const cellSizeFunctionParams: ZodObject<{
5
+ size: coerce.ZodCoercedNumber<unknown>;
6
+ }, import("zod/v4/core").$strip>;
7
+ export type InterpreterOptions = {
8
+ throwOnError?: boolean;
9
+ };
10
+ export declare class Interpreter {
11
+ private readonly ast;
12
+ private readonly assets;
13
+ private readonly source;
14
+ private errors;
15
+ private throwOnError;
16
+ constructor(ast: Call[][][], assets: Record<string, any> | undefined, source: string, options?: InterpreterOptions);
17
+ reportError(error: ChartaError): void;
18
+ getErrors(): ChartaError[];
19
+ private readonly normalizedSource;
20
+ private readonly sourceLines;
21
+ getSource(): string;
22
+ getLineTextForRow(row: number): string;
23
+ getWorldCellCenter(row: number, col: number): [number, number];
24
+ getRows(): number;
25
+ getCols(): number;
26
+ countCalls(index: [number, number] | undefined, filter: string | ((call: Call) => boolean), startAt?: number, endAtExcl?: number): number;
27
+ getCalls<T extends {
28
+ [Key in string]: ZodObject<any>;
29
+ }>(index: [number, number] | undefined, callSchemas: T): Array<{
30
+ [Key in keyof T]: [Key, output<T[Key]>, number, {
31
+ line: number;
32
+ column: number;
33
+ lineText: string;
34
+ }];
35
+ }[keyof T]>;
36
+ private getCellBounds;
37
+ private computeCallStartColumns;
38
+ getOptionalAsset<T>(constructor: {
39
+ new (...args: Array<any>): T;
40
+ }, name?: string): T | undefined;
41
+ getAsset<T>(constructor: {
42
+ new (...args: Array<any>): T;
43
+ }, name?: string, loc?: ErrorLocation): T | undefined;
44
+ setAsset(name: string, value: any): void;
45
+ getCellSize(): number;
46
+ }
47
+ //# sourceMappingURL=interpreter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpreter.d.ts","sourceRoot":"","sources":["../src/interpreter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,EAAE,SAAS,EAAa,MAAM,KAAK,CAAA;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAExD,eAAO,MAAM,sBAAsB;;gCAAoC,CAAA;AAEvE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IANzB,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,YAAY,CAAS;gBAGV,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,YAAK,EAChC,MAAM,EAAE,MAAM,EAC/B,OAAO,GAAE,kBAAuB;IAelC,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI;IAOrC,SAAS,IAAI,WAAW,EAAE;IAI1B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAU;IAEtC,SAAS,IAAI,MAAM;IAInB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAMtC,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAO9D,OAAO,IAAI,MAAM;IAKjB,OAAO,IAAI,MAAM;IAKjB,UAAU,CACR,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EACnC,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,EAC1C,OAAO,GAAE,MAAU,EACnB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM;IAgBT,QAAQ,CAAC,CAAC,SAAS;SAAG,GAAG,IAAI,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC;KAAE,EACpD,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EACnC,WAAW,EAAE,CAAC,GACb,KAAK,CACN;SACG,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;KACpG,CAAC,MAAM,CAAC,CAAC,CACX;IA0GD,OAAO,CAAC,aAAa;IAmBrB,OAAO,CAAC,uBAAuB;IAa/B,gBAAgB,CAAC,CAAC,EAAE,WAAW,EAAE;QAAE,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;KAAE,EAAE,IAAI,GAAE,MAAyB,GAAG,CAAC,GAAG,SAAS;IAWlH,QAAQ,CAAC,CAAC,EACR,WAAW,EAAE;QAAE,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;KAAE,EAC7C,IAAI,GAAE,MAAyB,EAC/B,GAAG,CAAC,EAAE,aAAa,GAClB,CAAC,GAAG,SAAS;IAmBhB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAIxC,WAAW,IAAI,MAAM;CAStB"}
@@ -0,0 +1,226 @@
1
+ import { coerce, object } from 'zod';
2
+ import { ChartaError } from './errors.js';
3
+ export const cellSizeFunctionParams = object({ size: coerce.number() });
4
+ export class Interpreter {
5
+ ast;
6
+ assets;
7
+ source;
8
+ errors = [];
9
+ throwOnError;
10
+ constructor(ast, assets = {}, source, options = {}) {
11
+ this.ast = ast;
12
+ this.assets = assets;
13
+ this.source = source;
14
+ this.throwOnError = options.throwOnError ?? true;
15
+ this.normalizedSource = this.source.replace(/\r\n?/g, '\n');
16
+ this.sourceLines = this.normalizedSource.split('\n');
17
+ if (this.ast.length === 0) {
18
+ this.reportError(new ChartaError(`Charta files must have atleast one line. Meta row is missing.`, this.normalizedSource, {
19
+ line: 1,
20
+ column: 1,
21
+ }));
22
+ }
23
+ }
24
+ reportError(error) {
25
+ this.errors.push(error);
26
+ if (this.throwOnError) {
27
+ throw error;
28
+ }
29
+ }
30
+ getErrors() {
31
+ return this.errors;
32
+ }
33
+ normalizedSource;
34
+ sourceLines;
35
+ getSource() {
36
+ return this.normalizedSource;
37
+ }
38
+ getLineTextForRow(row) {
39
+ // row is zero-based (grid row), line number is row+2
40
+ const lineNumber = row + 2;
41
+ return this.sourceLines[lineNumber - 1] ?? '';
42
+ }
43
+ getWorldCellCenter(row, col) {
44
+ const cellSize = this.getCellSize();
45
+ const worldCellCenterX = (col - this.getCols() / 2 + 0.5) * cellSize;
46
+ const worldCellCenterZ = (row - this.getRows() / 2 + 0.5) * cellSize;
47
+ return [worldCellCenterX, worldCellCenterZ];
48
+ }
49
+ getRows() {
50
+ // Exclude meta row
51
+ return this.ast.length - 1;
52
+ }
53
+ getCols() {
54
+ // Use first grid row, if present
55
+ return this.ast.length > 1 ? this.ast[1].length : 0;
56
+ }
57
+ countCalls(index, filter, startAt = 0, endAtExcl) {
58
+ let sum = 0;
59
+ const calls = index == null ? this.ast[0][0] : this.ast[index[0] + 1][index[1]];
60
+ const length = Math.min(endAtExcl ?? Infinity, calls.length);
61
+ for (let i = startAt; i < length; i++) {
62
+ if (typeof filter === 'string' && calls[i].name != filter) {
63
+ continue;
64
+ }
65
+ if (typeof filter === 'function' && !filter(calls[i])) {
66
+ continue;
67
+ }
68
+ sum++;
69
+ }
70
+ return sum;
71
+ }
72
+ getCalls(index, callSchemas) {
73
+ let calls;
74
+ const normalizedSource = this.normalizedSource;
75
+ const sourceLines = this.sourceLines;
76
+ let lineNumber = 1;
77
+ let lineText = sourceLines[0] ?? '';
78
+ let callStartColumns = [];
79
+ if (index == null) {
80
+ calls = this.ast[0][0];
81
+ callStartColumns = this.computeCallStartColumns(lineText);
82
+ }
83
+ else {
84
+ const row = index[0] + 1;
85
+ const col = index[1];
86
+ if (row >= this.ast.length || col >= this.ast[row].length) {
87
+ const lineNumber = index[0] + 2;
88
+ const lineText = sourceLines[lineNumber - 1] ?? '';
89
+ this.reportError(new ChartaError(`index ${col}/${row} cannot be used to index the ast (${this.ast.length}/${this.ast[Math.min(1, this.ast.length - 1)].length})`, normalizedSource, { line: lineNumber, column: 1, lineText }));
90
+ return []; // Return empty if index invalid
91
+ }
92
+ calls = this.ast[row][col];
93
+ lineNumber = index[0] + 2;
94
+ lineText = sourceLines[lineNumber - 1] ?? '';
95
+ const [cellStart, cellEnd] = this.getCellBounds(lineText, col);
96
+ const cellText = lineText.slice(cellStart, cellEnd);
97
+ callStartColumns = this.computeCallStartColumns(cellText).map((c) => c + cellStart);
98
+ }
99
+ return calls
100
+ .map((call, callIdx) => {
101
+ if (!(call.name in callSchemas)) {
102
+ return false;
103
+ }
104
+ const key = call.name;
105
+ const callSchema = callSchemas[key];
106
+ const result = {};
107
+ const entries = Object.entries(callSchema.shape);
108
+ for (let i = 0; i < call.locationParams.length; i++) {
109
+ if (i >= entries.length) {
110
+ const columnNumber = callStartColumns[callIdx] ?? 1;
111
+ this.reportError(new ChartaError(`unknown param for function "${call.name}" at index "${i}", expected max "${entries.length}" but received "${call.locationParams.length}"`, normalizedSource, { line: lineNumber, column: columnNumber, lineText }));
112
+ return false; // Skip this call
113
+ }
114
+ const [name, schema] = entries[i];
115
+ const parseResult = schema.safeParse(call.locationParams[i]);
116
+ if (parseResult.success) {
117
+ result[name] = parseResult.data;
118
+ }
119
+ else {
120
+ const columnNumber = callStartColumns[callIdx] ?? 1;
121
+ this.reportError(new ChartaError(`Invalid value for param "${name}": ${parseResult.error.message}`, normalizedSource, {
122
+ line: lineNumber,
123
+ column: columnNumber,
124
+ lineText,
125
+ }));
126
+ return false;
127
+ }
128
+ }
129
+ for (const [keyName, value] of call.keyParams) {
130
+ const entry = entries.find(([name]) => name === keyName);
131
+ if (entry == null) {
132
+ const columnNumber = callStartColumns[callIdx] ?? 1;
133
+ this.reportError(new ChartaError(`unknown param "${keyName}" for function "${call.name}"`, normalizedSource, {
134
+ line: lineNumber,
135
+ column: columnNumber,
136
+ lineText,
137
+ }));
138
+ return false;
139
+ }
140
+ const [name, schema] = entry;
141
+ const parseResult = schema.safeParse(value);
142
+ if (parseResult.success) {
143
+ result[name] = parseResult.data;
144
+ }
145
+ else {
146
+ const columnNumber = callStartColumns[callIdx] ?? 1;
147
+ this.reportError(new ChartaError(`Invalid value for param "${name}": ${parseResult.error.message}`, normalizedSource, {
148
+ line: lineNumber,
149
+ column: columnNumber,
150
+ lineText,
151
+ }));
152
+ return false;
153
+ }
154
+ }
155
+ const columnNumber = callStartColumns[callIdx] ?? 1;
156
+ return [key, result, callIdx, { line: lineNumber, column: columnNumber, lineText }];
157
+ })
158
+ .filter((value) => value != false);
159
+ }
160
+ getCellBounds(lineText, targetCellIndex) {
161
+ let currentCell = 0;
162
+ let start = 0;
163
+ for (let i = 0; i <= lineText.length; i++) {
164
+ if (i === lineText.length || lineText[i] === '|') {
165
+ if (currentCell === targetCellIndex) {
166
+ let s = start;
167
+ while (s < i && /\s/.test(lineText[s]))
168
+ s++;
169
+ let e = i;
170
+ while (e > s && /\s/.test(lineText[e - 1]))
171
+ e--;
172
+ return [s, e];
173
+ }
174
+ currentCell++;
175
+ start = i + 1;
176
+ }
177
+ }
178
+ return [0, lineText.length];
179
+ }
180
+ computeCallStartColumns(text) {
181
+ const columns = [];
182
+ const re = /[a-z0-9_\/-]+\s*\(/g;
183
+ let m;
184
+ while ((m = re.exec(text)) != null) {
185
+ const match = m[0];
186
+ const identStartOffset = match.search(/[a-z0-9_\/-]/);
187
+ const col = m.index + identStartOffset + 1; // 1-based
188
+ columns.push(col);
189
+ }
190
+ return columns;
191
+ }
192
+ getOptionalAsset(constructor, name = constructor.name) {
193
+ const asset = this.assets[name];
194
+ if (asset == null) {
195
+ return undefined;
196
+ }
197
+ if (!(asset instanceof constructor)) {
198
+ return undefined;
199
+ }
200
+ return asset;
201
+ }
202
+ getAsset(constructor, name = constructor.name, loc) {
203
+ const asset = this.assets[name];
204
+ if (asset == null) {
205
+ this.reportError(new ChartaError(`unknown asset "${name}"`, this.normalizedSource, loc));
206
+ return undefined;
207
+ }
208
+ if (!(asset instanceof constructor)) {
209
+ this.reportError(new ChartaError(`expected the type ${constructor.name} for asset "${name}"`, this.normalizedSource, loc));
210
+ return undefined;
211
+ }
212
+ return asset;
213
+ }
214
+ setAsset(name, value) {
215
+ this.assets[name] = value;
216
+ }
217
+ getCellSize() {
218
+ let cellSize = 1;
219
+ for (const [, { size }] of this.getCalls(undefined, {
220
+ cellSize: cellSizeFunctionParams,
221
+ })) {
222
+ cellSize = size;
223
+ }
224
+ return cellSize;
225
+ }
226
+ }
@@ -0,0 +1,16 @@
1
+ import { Interpreter } from './interpreter.js';
2
+ import { Vector3 } from 'three';
3
+ export declare class Locations {
4
+ private readonly interpreter;
5
+ private readonly nameToCell;
6
+ constructor(interpreter: Interpreter);
7
+ /**
8
+ * Resolve a named location to a world-space position (x, y, z).
9
+ * - X/Z are the center of the owning cell.
10
+ * - Y is sampled from the nearest relevant layer stack at that cell.
11
+ *
12
+ * If terrain data is unavailable, Y defaults to 0.
13
+ */
14
+ getWorldPosition(name: string, target: Vector3): Vector3;
15
+ }
16
+ //# sourceMappingURL=locations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../src/locations.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAG9C,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAI/B,qBAAa,SAAS;IAGR,OAAO,CAAC,QAAQ,CAAC,WAAW;IAFxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;gBAEhC,WAAW,EAAE,WAAW;IAsBrD;;;;;;OAMG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO;CAwBzD"}
@@ -0,0 +1,58 @@
1
+ import { object, string } from 'zod';
2
+ import { TilesGeometry } from './tiles/geometry.js';
3
+ import { ChartaError } from './errors.js';
4
+ export class Locations {
5
+ interpreter;
6
+ nameToCell = new Map();
7
+ constructor(interpreter) {
8
+ this.interpreter = interpreter;
9
+ const rows = interpreter.getRows();
10
+ const cols = interpreter.getCols();
11
+ const locationSchema = object({ name: string() });
12
+ for (let row = 0; row < rows; row++) {
13
+ for (let col = 0; col < cols; col++) {
14
+ const entries = interpreter.getCalls([row, col], {
15
+ location: locationSchema,
16
+ });
17
+ for (const [, parsed, callIdx, loc] of entries) {
18
+ const name = parsed.name;
19
+ if (this.nameToCell.has(name)) {
20
+ interpreter.reportError(new ChartaError(`duplicate location "${name}"`, interpreter.getSource(), loc));
21
+ continue;
22
+ }
23
+ this.nameToCell.set(name, { row, col, callIdx });
24
+ }
25
+ }
26
+ }
27
+ }
28
+ /**
29
+ * Resolve a named location to a world-space position (x, y, z).
30
+ * - X/Z are the center of the owning cell.
31
+ * - Y is sampled from the nearest relevant layer stack at that cell.
32
+ *
33
+ * If terrain data is unavailable, Y defaults to 0.
34
+ */
35
+ getWorldPosition(name, target) {
36
+ const cellLoc = this.nameToCell.get(name);
37
+ if (!cellLoc) {
38
+ throw new Error(`Unknown location "${name}".`);
39
+ }
40
+ const { row, col, callIdx } = cellLoc;
41
+ const [x, z] = this.interpreter.getWorldCellCenter(row, col);
42
+ // Determine the stack index similarly to how walls/pillars derive defaults.
43
+ const isLayer = (c) => c.name === 'ground' || c.name === 'ceiling';
44
+ const layersBefore = this.interpreter.countCalls([row, col], isLayer, 0, callIdx);
45
+ let stackIndex;
46
+ if (layersBefore > 0) {
47
+ stackIndex = layersBefore - 1;
48
+ }
49
+ else {
50
+ const layersAfter = this.interpreter.countCalls([row, col], isLayer, callIdx + 1);
51
+ stackIndex = layersAfter > 0 ? 0 : 0;
52
+ }
53
+ const tilesGeometry = this.interpreter.getAsset(TilesGeometry, 'tilesGeometry');
54
+ const y = tilesGeometry?.getHeight(col, row, stackIndex, 0, 0) ?? 0;
55
+ target.set(x, y, z);
56
+ return target;
57
+ }
58
+ }
@@ -0,0 +1,9 @@
1
+ export type KeyParam = [key: string, value: string];
2
+ export type Call = {
3
+ name: string;
4
+ locationParams: string[];
5
+ keyParams: KeyParam[];
6
+ offset: number;
7
+ };
8
+ export declare function parse(input: string): Call[][][];
9
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AACpD,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAKF,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,CA0C/C"}
package/dist/parser.js ADDED
@@ -0,0 +1,47 @@
1
+ import nearley from "nearley";
2
+ const { Parser, Grammar } = nearley;
3
+ import grammar from "./grammar.js";
4
+ import { ChartaError } from "./errors.js";
5
+ export function parse(input) {
6
+ const normalized = input.replace(/\r\n?/g, "\n");
7
+ const parser = new Parser(Grammar.fromCompiled(grammar));
8
+ let rows;
9
+ try {
10
+ rows = parser.feed(normalized).finish()[0];
11
+ }
12
+ catch (error) {
13
+ const loc = typeof error.offset === 'number'
14
+ ? getLoc(normalized, error.offset)
15
+ : undefined;
16
+ throw new ChartaError(error.message || "Parse error", input, loc);
17
+ }
18
+ if (!rows || rows.length === 0)
19
+ return [];
20
+ // Validate Meta Row (first row)
21
+ if (rows[0].length !== 1) {
22
+ throwError(input, rows[0].offset, `First row must contain exactly one cell (meta row), found ${rows[0].length}`);
23
+ }
24
+ // Validate Grid Rows (subsequent rows)
25
+ // All grid rows must have the same number of cells as the first grid row
26
+ if (rows.length > 1) {
27
+ const gridRows = rows.slice(1);
28
+ const expectedWidth = gridRows[0].length;
29
+ const badRowIdx = gridRows.findIndex(r => r.length !== expectedWidth);
30
+ if (badRowIdx !== -1) {
31
+ throwError(input, gridRows[badRowIdx].offset, `Row ${badRowIdx + 2} has ${gridRows[badRowIdx].length} cells; expected ${expectedWidth}`);
32
+ }
33
+ }
34
+ return rows;
35
+ }
36
+ function throwError(input, offset, message) {
37
+ throw new ChartaError(message, input, getLoc(input, offset));
38
+ }
39
+ function getLoc(input, offset) {
40
+ const clampedOffset = Math.max(0, Math.min(offset, input.length));
41
+ const prefix = input.slice(0, clampedOffset);
42
+ const lines = prefix.split('\n');
43
+ return {
44
+ line: lines.length,
45
+ column: lines[lines.length - 1].length + 1
46
+ };
47
+ }
@@ -0,0 +1,7 @@
1
+ import { InstancedMesh, Material } from 'three';
2
+ import { Interpreter } from '../interpreter.js';
3
+ export type PillarCorner = 'topleft' | 'topright' | 'bottomright' | 'bottomleft';
4
+ export declare class PillarMesh extends InstancedMesh {
5
+ constructor(interpreter: Interpreter, material?: Material);
6
+ }
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pillars/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EAEb,QAAQ,EAMT,MAAM,OAAO,CAAA;AACd,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAO/C,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,GAAG,YAAY,CAAA;AAuBhF,qBAAa,UAAW,SAAQ,aAAa;gBAC/B,WAAW,EAAE,WAAW,EAAE,QAAQ,GAAE,QAAkC;CAoKnF"}
@@ -0,0 +1,154 @@
1
+ import { BoxGeometry, InstancedMesh, InstancedBufferAttribute, Matrix4, MeshBasicMaterial, Quaternion, Vector3, Texture, } from 'three';
2
+ import { TilesGeometry } from '../tiles/geometry.js';
3
+ import { coerce, object, string, z } from 'zod';
4
+ import { buildTextureArrayFromAssets } from '../utils/texture.js';
5
+ import { buildPillarMeshMaterial } from './material.js';
6
+ import { ChartaError } from '../errors.js';
7
+ const cornerEnum = z.enum(['topleft', 'topright', 'bottomright', 'bottomleft']);
8
+ const pillarSchema = object({
9
+ corner: cornerEnum, // positional 0
10
+ texture: string(), // positional 1
11
+ sizeX: coerce.number().optional(),
12
+ sizeZ: coerce.number().optional(),
13
+ bottomY: coerce.number().optional(),
14
+ topY: coerce.number().optional(),
15
+ });
16
+ export class PillarMesh extends InstancedMesh {
17
+ constructor(interpreter, material = new MeshBasicMaterial()) {
18
+ const rows = interpreter.getRows();
19
+ const cols = interpreter.getCols();
20
+ const cellSize = interpreter.getCellSize();
21
+ // Collect pillar instances with resolved heights
22
+ const pillars = [];
23
+ // tilesGeometry is only required for height sampling; fetch lazily where needed
24
+ const usedTextures = [];
25
+ const getTextureId = (name) => {
26
+ const texture = interpreter.getAsset(Texture, `${name}BaseColorTexture`);
27
+ if (!texture)
28
+ return 0;
29
+ let idx = usedTextures.indexOf(texture);
30
+ if (idx === -1) {
31
+ idx = usedTextures.length;
32
+ usedTextures.push(texture);
33
+ }
34
+ return idx;
35
+ };
36
+ function resolveCornerPosition(row, col, corner) {
37
+ const [cx, cz] = interpreter.getWorldCellCenter(row, col);
38
+ const half = cellSize * 0.5;
39
+ switch (corner) {
40
+ case 'topleft':
41
+ return [cx - half, cz - half];
42
+ case 'topright':
43
+ return [cx + half, cz - half];
44
+ case 'bottomright':
45
+ return [cx + half, cz + half];
46
+ case 'bottomleft':
47
+ return [cx - half, cz + half];
48
+ default: {
49
+ const lineNumber = row + 2;
50
+ const lineText = interpreter.getLineTextForRow(row);
51
+ const pos = lineText.indexOf('pillar(');
52
+ const columnNumber = pos >= 0 ? pos + 1 : 1;
53
+ interpreter.reportError(new ChartaError(`pillar at ${row}/${col}: invalid corner "${corner}", expected one of topleft, topright, bottomright, bottomleft`, interpreter.getSource(), { line: lineNumber, column: columnNumber, lineText }));
54
+ return undefined;
55
+ }
56
+ }
57
+ }
58
+ for (let row = 0; row < rows; row++) {
59
+ for (let col = 0; col < cols; col++) {
60
+ const entries = interpreter.getCalls([row, col], {
61
+ pillar: pillarSchema,
62
+ });
63
+ for (const [, parsed, idx, loc] of entries) {
64
+ const sizeX = Math.max(1e-4, parsed.sizeX ?? 0.2);
65
+ const sizeZ = Math.max(1e-4, parsed.sizeZ ?? 0.2);
66
+ const cornerPos = resolveCornerPosition(row, col, parsed.corner);
67
+ if (!cornerPos)
68
+ continue;
69
+ const [px, pz] = cornerPos;
70
+ const [cx, cz] = interpreter.getWorldCellCenter(row, col);
71
+ const offsetX = px - cx;
72
+ const offsetZ = pz - cz;
73
+ const textureId = getTextureId(parsed.texture);
74
+ // Derive default layers relative to call position (like walls)
75
+ const isLayer = (c) => c.name === 'ground' || c.name === 'ceiling';
76
+ let bottomY = parsed.bottomY;
77
+ let topY = parsed.topY;
78
+ let bottomStackIndex;
79
+ if (bottomY == null) {
80
+ const layersBefore = interpreter.countCalls([row, col], isLayer, 0, idx);
81
+ bottomStackIndex = layersBefore - 1;
82
+ if (bottomStackIndex < 0) {
83
+ interpreter.reportError(new ChartaError(`pillar at ${row}/${col}: missing bottomY and no preceding layer`, interpreter.getSource(), loc));
84
+ continue;
85
+ }
86
+ const tilesGeometry = interpreter.getAsset(TilesGeometry, 'tilesGeometry', loc);
87
+ if (!tilesGeometry)
88
+ continue;
89
+ bottomY = tilesGeometry.getHeight(col, row, bottomStackIndex, offsetX, offsetZ);
90
+ }
91
+ let topStackIndex;
92
+ if (topY == null) {
93
+ const layersAfter = interpreter.countCalls([row, col], isLayer, idx + 1);
94
+ if (layersAfter <= 0) {
95
+ interpreter.reportError(new ChartaError(`pillar at ${row}/${col}: missing topY and no subsequent layer`, interpreter.getSource(), loc));
96
+ continue;
97
+ }
98
+ const layersBefore = interpreter.countCalls([row, col], isLayer, 0, idx);
99
+ topStackIndex = layersBefore;
100
+ const tilesGeometry = interpreter.getAsset(TilesGeometry, 'tilesGeometry', loc);
101
+ if (!tilesGeometry)
102
+ continue;
103
+ topY = tilesGeometry.getHeight(col, row, topStackIndex, offsetX, offsetZ);
104
+ }
105
+ // Support "negative pillars" by swapping
106
+ if (topY < bottomY) {
107
+ const tmp = bottomY;
108
+ bottomY = topY;
109
+ topY = tmp;
110
+ }
111
+ pillars.push({
112
+ x: px,
113
+ z: pz,
114
+ sizeX,
115
+ sizeZ,
116
+ bottomY: bottomY,
117
+ topY: topY,
118
+ textureId,
119
+ });
120
+ }
121
+ }
122
+ }
123
+ // Base cube with base at y=0 for easy scaling
124
+ const geometry = new BoxGeometry(1, 1, 1);
125
+ geometry.translate(0, 0.5, 0);
126
+ super(geometry, material, pillars.length);
127
+ const tmpPos = new Vector3();
128
+ const tmpQuat = new Quaternion();
129
+ const tmpScale = new Vector3();
130
+ const tmpMat = new Matrix4();
131
+ // Per-instance transforms and material indices
132
+ if (pillars.length > 0) {
133
+ const texIds = new Float32Array(pillars.length);
134
+ for (let i = 0; i < pillars.length; i++) {
135
+ const p = pillars[i];
136
+ const height = Math.max(0, p.topY - p.bottomY);
137
+ tmpPos.set(p.x, p.bottomY, p.z);
138
+ tmpQuat.identity();
139
+ tmpScale.set(p.sizeX, height, p.sizeZ);
140
+ tmpMat.compose(tmpPos, tmpQuat, tmpScale);
141
+ this.setMatrixAt(i, tmpMat);
142
+ texIds[i] = p.textureId;
143
+ }
144
+ ;
145
+ this.geometry.setAttribute('instanceTextureId', new InstancedBufferAttribute(texIds, 1));
146
+ }
147
+ this.instanceMatrix.needsUpdate = true;
148
+ // Texture array setup (if materials used)
149
+ if (usedTextures.length > 0) {
150
+ const textureArray = buildTextureArrayFromAssets(usedTextures);
151
+ buildPillarMeshMaterial(this.material, textureArray);
152
+ }
153
+ }
154
+ }
@@ -0,0 +1,3 @@
1
+ import { Material } from "three";
2
+ export declare function buildPillarMeshMaterial(material: Material, textureArray: any): void;
3
+ //# sourceMappingURL=material.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"material.d.ts","sourceRoot":"","sources":["../../src/pillars/material.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,QAAQ,EAClB,YAAY,EAAE,GAAG,GAChB,IAAI,CAmDN"}