@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
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2024 drawcall.ai
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,21 @@
1
+ import { Group } from 'three';
2
+ import { Interpreter } from '../interpreter.js';
3
+ export type AssetLoaderOptions = {
4
+ mock?: boolean;
5
+ };
6
+ export declare class AssetLoader extends Group {
7
+ private interpreter;
8
+ private options;
9
+ private manager;
10
+ private textureLoader;
11
+ private gltfLoader;
12
+ private _whenReady;
13
+ private readonly pendingFinalizers;
14
+ constructor(interpreter: Interpreter, options?: AssetLoaderOptions);
15
+ updateMatrixWorld(force?: boolean): void;
16
+ private handleLoadTexture;
17
+ private handleLoadModel;
18
+ whenReady(): Promise<void>;
19
+ dispose(): void;
20
+ }
21
+ //# sourceMappingURL=loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/assets/loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAA6D,MAAM,OAAO,CAAA;AAExF,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAM/C,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,CAAC,EAAE,OAAO,CAAA;CACf,CAAA;AAED,qBAAa,WAAY,SAAQ,KAAK;IAOxB,OAAO,CAAC,WAAW;IAAe,OAAO,CAAC,OAAO;IAN7D,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,UAAU,CAAY;IAC9B,OAAO,CAAC,UAAU,CAAe;IACjC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwB;gBAEtC,WAAW,EAAE,WAAW,EAAU,OAAO,GAAE,kBAAuB;IAmCtF,iBAAiB,CAAC,KAAK,CAAC,EAAE,OAAO;YAUnB,iBAAiB;YA8BjB,eAAe;IA6CvB,SAAS;IAIf,OAAO;CAQR"}
@@ -0,0 +1,113 @@
1
+ import { Group, Texture, TextureLoader, LoadingManager, Object3D, Matrix4 } from 'three';
2
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
3
+ import { ChartaError } from '../errors.js';
4
+ import { ObjectFactory } from '../place/index.js';
5
+ import { InstancedMeshGroup } from '../utils/instanced-mesh-group.js';
6
+ import { object, string } from 'zod';
7
+ export class AssetLoader extends Group {
8
+ interpreter;
9
+ options;
10
+ manager;
11
+ textureLoader;
12
+ gltfLoader;
13
+ _whenReady;
14
+ pendingFinalizers = [];
15
+ constructor(interpreter, options = {}) {
16
+ super();
17
+ this.interpreter = interpreter;
18
+ this.options = options;
19
+ this.manager = new LoadingManager();
20
+ this.textureLoader = new TextureLoader(this.manager);
21
+ this.gltfLoader = new GLTFLoader(this.manager);
22
+ const promises = [];
23
+ // Schemas
24
+ const basePathSchema = object({ path: string() });
25
+ const loadTextureSchema = object({ name: string(), path: string() });
26
+ const loadModelSchema = object({ name: string(), path: string() });
27
+ // Parse meta row calls
28
+ const calls = interpreter.getCalls(undefined, {
29
+ basePath: basePathSchema,
30
+ loadTexture: loadTextureSchema,
31
+ loadModel: loadModelSchema,
32
+ });
33
+ let basePath;
34
+ for (const [key, args, , location] of calls) {
35
+ if (key === 'basePath') {
36
+ basePath = args.path.length === 0 ? undefined : args.path;
37
+ }
38
+ else if (key === 'loadTexture') {
39
+ promises.push(this.handleLoadTexture(args.name, basePath, args.path, location));
40
+ }
41
+ else if (key === 'loadModel') {
42
+ promises.push(this.handleLoadModel(args.name, basePath, args.path, location));
43
+ }
44
+ }
45
+ this._whenReady = Promise.all(promises).then(() => { });
46
+ }
47
+ updateMatrixWorld(force) {
48
+ if (this.pendingFinalizers.length > 0) {
49
+ for (const finalize of this.pendingFinalizers) {
50
+ finalize();
51
+ }
52
+ this.pendingFinalizers.length = 0;
53
+ }
54
+ super.updateMatrixWorld(force);
55
+ }
56
+ async handleLoadTexture(name, basePath, path, location) {
57
+ if (this.options.mock) {
58
+ const texture = new Texture();
59
+ texture.name = name;
60
+ this.interpreter.setAsset(`${name}BaseColorTexture`, texture);
61
+ return;
62
+ }
63
+ const fullPath = new URL(path, basePath).href;
64
+ try {
65
+ const texture = await this.textureLoader.loadAsync(fullPath);
66
+ texture.name = name;
67
+ this.interpreter.setAsset(`${name}BaseColorTexture`, texture);
68
+ }
69
+ catch (err) {
70
+ this.interpreter.reportError(new ChartaError(`Failed to load texture "${name}" at "${fullPath}". Is the url correct?`, this.interpreter.getSource(), location));
71
+ }
72
+ }
73
+ async handleLoadModel(name, basePath, path, location) {
74
+ if (this.options.mock) {
75
+ const factory = new ObjectFactory(new Object3D(), () => { });
76
+ this.interpreter.setAsset(`${name}Factory`, factory);
77
+ return;
78
+ }
79
+ const fullPath = new URL(path, basePath).href;
80
+ try {
81
+ const gltf = await this.gltfLoader.loadAsync(fullPath);
82
+ const scene = gltf.scene;
83
+ scene.traverse((object) => {
84
+ object.castShadow = true;
85
+ object.receiveShadow = true;
86
+ });
87
+ const matrices = [];
88
+ const factory = new ObjectFactory(scene, ({ position, rotation, scale }) => {
89
+ matrices.push(new Matrix4().compose(position, rotation, scale));
90
+ });
91
+ this.interpreter.setAsset(`${name}Factory`, factory);
92
+ this.pendingFinalizers.push(() => {
93
+ if (matrices.length > 0) {
94
+ this.add(new InstancedMeshGroup(scene, matrices.length, matrices));
95
+ }
96
+ });
97
+ }
98
+ catch (err) {
99
+ this.interpreter.reportError(new ChartaError(`Failed to load model ${name} at ${fullPath}: ${err instanceof Error ? err.message : String(err)}`, this.interpreter.getSource(), location));
100
+ }
101
+ }
102
+ async whenReady() {
103
+ return this._whenReady;
104
+ }
105
+ dispose() {
106
+ this.traverse((child) => {
107
+ if (child instanceof InstancedMeshGroup) {
108
+ child.dispose();
109
+ }
110
+ });
111
+ this.clear();
112
+ }
113
+ }
@@ -0,0 +1,11 @@
1
+ export type ErrorLocation = {
2
+ line: number;
3
+ column: number;
4
+ lineText?: string;
5
+ };
6
+ export declare class ChartaError extends Error {
7
+ readonly source: string;
8
+ readonly loc?: ErrorLocation | undefined;
9
+ constructor(causeMessage: string, source: string, loc?: ErrorLocation | undefined);
10
+ }
11
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhF,qBAAa,WAAY,SAAQ,KAAK;aAGlB,MAAM,EAAE,MAAM;aACd,GAAG,CAAC,EAAE,aAAa;gBAFnC,YAAY,EAAE,MAAM,EACJ,MAAM,EAAE,MAAM,EACd,GAAG,CAAC,EAAE,aAAa,YAAA;CAqBtC"}
package/dist/errors.js ADDED
@@ -0,0 +1,27 @@
1
+ export class ChartaError extends Error {
2
+ source;
3
+ loc;
4
+ constructor(causeMessage, source, loc) {
5
+ let message;
6
+ if (loc == null) {
7
+ message = causeMessage;
8
+ }
9
+ else {
10
+ const normalized = source.replace(/\r\n?/g, "\n");
11
+ const lines = normalized.split("\n");
12
+ const lineText = loc.lineText ?? lines[loc.line - 1] ?? "";
13
+ const prefix = `${loc.line} | `;
14
+ const caret = " ".repeat(prefix.length + loc.column - 1) + "^";
15
+ message = [
16
+ causeMessage,
17
+ `at line ${loc.line}, column ${loc.column}`,
18
+ `${prefix}${lineText}`,
19
+ caret,
20
+ ].join("\n");
21
+ }
22
+ super(message);
23
+ this.source = source;
24
+ this.loc = loc;
25
+ this.name = "ChartaError";
26
+ }
27
+ }
@@ -0,0 +1,29 @@
1
+ interface NearleyToken {
2
+ value: any;
3
+ [key: string]: any;
4
+ }
5
+ interface NearleyLexer {
6
+ reset: (chunk: string, info: any) => void;
7
+ next: () => NearleyToken | undefined;
8
+ save: () => any;
9
+ formatError: (token: never) => string;
10
+ has: (tokenType: string) => boolean;
11
+ }
12
+ interface NearleyRule {
13
+ name: string;
14
+ symbols: NearleySymbol[];
15
+ postprocess?: (d: any[], loc?: number, reject?: {}) => any;
16
+ }
17
+ type NearleySymbol = string | {
18
+ literal: any;
19
+ } | {
20
+ test: (token: any) => boolean;
21
+ };
22
+ interface Grammar {
23
+ Lexer: NearleyLexer | undefined;
24
+ ParserRules: NearleyRule[];
25
+ ParserStart: string;
26
+ }
27
+ declare const grammar: Grammar;
28
+ export default grammar;
29
+ //# sourceMappingURL=grammar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../src/grammar.ts"],"names":[],"mappings":"AAOA,UAAU,YAAY;IACpB,KAAK,EAAE,GAAG,CAAC;IACX,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC1C,IAAI,EAAE,MAAM,YAAY,GAAG,SAAS,CAAC;IACrC,IAAI,EAAE,MAAM,GAAG,CAAC;IAChB,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC;IACtC,GAAG,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;CACrC;AAED,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC;CAC5D;AAED,KAAK,aAAa,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,GAAG,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAA;CAAE,CAAC;AAEnF,UAAU,OAAO;IACf,KAAK,EAAE,YAAY,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,WAAW,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,QAAA,MAAM,OAAO,EAAE,OAwGd,CAAC;AAEF,eAAe,OAAO,CAAC"}
@@ -0,0 +1,119 @@
1
+ // @ts-nocheck
2
+ // Generated automatically by nearley, version 2.20.1
3
+ // http://github.com/Hardmath123/nearley
4
+ // Bypasses TS6133. Allow declared but unused functions.
5
+ // @ts-ignore
6
+ function id(d) { return d[0]; }
7
+ ;
8
+ ;
9
+ ;
10
+ ;
11
+ const grammar = {
12
+ Lexer: undefined,
13
+ ParserRules: [
14
+ { "name": "File$ebnf$1", "symbols": ["NL"], "postprocess": id },
15
+ { "name": "File$ebnf$1", "symbols": [], "postprocess": () => null },
16
+ { "name": "File", "symbols": ["Rows", "File$ebnf$1"], "postprocess": (d) => d[0] },
17
+ { "name": "NLS$ebnf$1", "symbols": ["NL"] },
18
+ { "name": "NLS$ebnf$1", "symbols": ["NLS$ebnf$1", "NL"], "postprocess": (d) => d[0].concat([d[1]]) },
19
+ { "name": "NLS", "symbols": ["NLS$ebnf$1"], "postprocess": () => null },
20
+ { "name": "NL", "symbols": [{ "literal": "\n" }] },
21
+ { "name": "Rows$ebnf$1", "symbols": [] },
22
+ { "name": "Rows$ebnf$1$subexpression$1", "symbols": ["NLS", "Row"] },
23
+ { "name": "Rows$ebnf$1", "symbols": ["Rows$ebnf$1", "Rows$ebnf$1$subexpression$1"], "postprocess": (d) => d[0].concat([d[1]]) },
24
+ { "name": "Rows", "symbols": ["Row", "Rows$ebnf$1"], "postprocess": ([head, tail]) => [head, ...tail.map(([, row]) => row)] },
25
+ { "name": "Row$ebnf$1", "symbols": [] },
26
+ { "name": "Row$ebnf$1", "symbols": ["Row$ebnf$1", "WS"], "postprocess": (d) => d[0].concat([d[1]]) },
27
+ { "name": "Row$ebnf$2", "symbols": [] },
28
+ { "name": "Row$ebnf$2$subexpression$1", "symbols": ["PIPESep", "Cell"] },
29
+ { "name": "Row$ebnf$2", "symbols": ["Row$ebnf$2", "Row$ebnf$2$subexpression$1"], "postprocess": (d) => d[0].concat([d[1]]) },
30
+ { "name": "Row$ebnf$3", "symbols": [] },
31
+ { "name": "Row$ebnf$3", "symbols": ["Row$ebnf$3", "WS"], "postprocess": (d) => d[0].concat([d[1]]) },
32
+ { "name": "Row", "symbols": ["Row$ebnf$1", "Cell", "Row$ebnf$2", "Row$ebnf$3"], "postprocess": (d, loc) => {
33
+ const head = d[1];
34
+ const tail = d[2];
35
+ const row = [head, ...tail.map(([, cell]) => cell)];
36
+ row.offset = loc;
37
+ return row;
38
+ } },
39
+ { "name": "Cell$ebnf$1", "symbols": ["Calls"], "postprocess": id },
40
+ { "name": "Cell$ebnf$1", "symbols": [], "postprocess": () => null },
41
+ { "name": "Cell", "symbols": ["Cell$ebnf$1"], "postprocess": (d) => d[0] || [] },
42
+ { "name": "Calls$ebnf$1", "symbols": [] },
43
+ { "name": "Calls$ebnf$1$subexpression$1", "symbols": ["SP", "Call"] },
44
+ { "name": "Calls$ebnf$1", "symbols": ["Calls$ebnf$1", "Calls$ebnf$1$subexpression$1"], "postprocess": (d) => d[0].concat([d[1]]) },
45
+ { "name": "Calls", "symbols": ["Call", "Calls$ebnf$1"], "postprocess": ([first, rest]) => [first, ...rest.map(([, c]) => c)] },
46
+ { "name": "Call", "symbols": ["Ident", "LPAREN", "ArgsBlock", "RPAREN"], "postprocess": (d, loc) => {
47
+ const args = d[2];
48
+ return { name: d[0], locationParams: args.pos, keyParams: args.kv, offset: loc };
49
+ } },
50
+ { "name": "ArgList$ebnf$1", "symbols": [] },
51
+ { "name": "ArgList$ebnf$1$subexpression$1", "symbols": ["COMMASEP", "Arg"] },
52
+ { "name": "ArgList$ebnf$1", "symbols": ["ArgList$ebnf$1", "ArgList$ebnf$1$subexpression$1"], "postprocess": (d) => d[0].concat([d[1]]) },
53
+ { "name": "ArgList", "symbols": ["Arg", "ArgList$ebnf$1"], "postprocess": (d) => {
54
+ const items = [d[0], ...d[1].map((x) => x[1])];
55
+ const pos = [];
56
+ const kv = [];
57
+ for (const a of items) {
58
+ if (a && a.kind === 'kv')
59
+ kv.push([a.key, a.value]);
60
+ else
61
+ pos.push(a);
62
+ }
63
+ return { pos, kv };
64
+ } },
65
+ { "name": "ArgsBlock", "symbols": ["_", "ArgList", "_"], "postprocess": (d) => d[1] },
66
+ { "name": "ArgsBlock", "symbols": ["_"], "postprocess": () => ({ pos: [], kv: [] }) },
67
+ { "name": "Arg", "symbols": ["Ident", "WSOpt", "EQ", "WSOpt", "Value"], "postprocess": (d) => ({ kind: 'kv', key: d[0], value: d[4] }) },
68
+ { "name": "Arg", "symbols": ["Value"], "postprocess": (d) => d[0] },
69
+ { "name": "Value", "symbols": ["UnescapedString"], "postprocess": (d) => d[0] },
70
+ { "name": "Value", "symbols": ["EscapedString"], "postprocess": (d) => d[0] },
71
+ { "name": "EscapedString", "symbols": ["QUOTE", "EscapedStringContent", "QUOTE"], "postprocess": (d) => d[1] },
72
+ { "name": "EscapedStringContent$ebnf$1", "symbols": [/[^"]/] },
73
+ { "name": "EscapedStringContent$ebnf$1", "symbols": ["EscapedStringContent$ebnf$1", /[^"]/], "postprocess": (d) => d[0].concat([d[1]]) },
74
+ { "name": "EscapedStringContent", "symbols": ["EscapedStringContent$ebnf$1"], "postprocess": (d) => d[0].join("") },
75
+ { "name": "QUOTE", "symbols": [{ "literal": "\"" }] },
76
+ { "name": "UnescapedString$ebnf$1", "symbols": ["UnescapedStringChar"] },
77
+ { "name": "UnescapedString$ebnf$1", "symbols": ["UnescapedString$ebnf$1", "UnescapedStringChar"], "postprocess": (d) => d[0].concat([d[1]]) },
78
+ { "name": "UnescapedString", "symbols": ["UnescapedString$ebnf$1"], "postprocess": (d) => d[0].join("") },
79
+ { "name": "UnescapedStringChar", "symbols": [/[^ \t,()=|\n"]/] },
80
+ { "name": "LPAREN", "symbols": [{ "literal": "(" }] },
81
+ { "name": "RPAREN", "symbols": [{ "literal": ")" }] },
82
+ { "name": "PIPE", "symbols": [{ "literal": "|" }] },
83
+ { "name": "COMMA", "symbols": [{ "literal": "," }] },
84
+ { "name": "EQ", "symbols": [{ "literal": "=" }] },
85
+ { "name": "PIPESep$ebnf$1", "symbols": [] },
86
+ { "name": "PIPESep$ebnf$1", "symbols": ["PIPESep$ebnf$1", "WS"], "postprocess": (d) => d[0].concat([d[1]]) },
87
+ { "name": "PIPESep$ebnf$2", "symbols": [] },
88
+ { "name": "PIPESep$ebnf$2", "symbols": ["PIPESep$ebnf$2", "WS"], "postprocess": (d) => d[0].concat([d[1]]) },
89
+ { "name": "PIPESep", "symbols": ["PIPESep$ebnf$1", "PIPE", "PIPESep$ebnf$2"] },
90
+ { "name": "COMMASEP$ebnf$1", "symbols": [] },
91
+ { "name": "COMMASEP$ebnf$1", "symbols": ["COMMASEP$ebnf$1", "WS"], "postprocess": (d) => d[0].concat([d[1]]) },
92
+ { "name": "COMMASEP$ebnf$2", "symbols": [] },
93
+ { "name": "COMMASEP$ebnf$2", "symbols": ["COMMASEP$ebnf$2", "WS"], "postprocess": (d) => d[0].concat([d[1]]) },
94
+ { "name": "COMMASEP", "symbols": ["COMMASEP$ebnf$1", "COMMA", "COMMASEP$ebnf$2"] },
95
+ { "name": "Ident$ebnf$1", "symbols": [] },
96
+ { "name": "Ident$ebnf$1", "symbols": ["Ident$ebnf$1", "IdentRest"], "postprocess": (d) => d[0].concat([d[1]]) },
97
+ { "name": "Ident", "symbols": ["IdentStart", "Ident$ebnf$1"], "postprocess": ([start, rest]) => start + rest.join("") },
98
+ { "name": "IdentStart", "symbols": [/[a-z_\/]/] },
99
+ { "name": "IdentRest", "symbols": [/[a-z0-9A-Z_\/-]/] },
100
+ { "name": "SP$ebnf$1", "symbols": ["WS"] },
101
+ { "name": "SP$ebnf$1", "symbols": ["SP$ebnf$1", "WS"], "postprocess": (d) => d[0].concat([d[1]]) },
102
+ { "name": "SP", "symbols": ["SP$ebnf$1"], "postprocess": () => null },
103
+ { "name": "WSOpt$ebnf$1", "symbols": [] },
104
+ { "name": "WSOpt$ebnf$1", "symbols": ["WSOpt$ebnf$1", "WS"], "postprocess": (d) => d[0].concat([d[1]]) },
105
+ { "name": "WSOpt", "symbols": ["WSOpt$ebnf$1"], "postprocess": () => null },
106
+ { "name": "_$ebnf$1", "symbols": [] },
107
+ { "name": "_$ebnf$1$subexpression$1", "symbols": ["_unit"] },
108
+ { "name": "_$ebnf$1", "symbols": ["_$ebnf$1", "_$ebnf$1$subexpression$1"], "postprocess": (d) => d[0].concat([d[1]]) },
109
+ { "name": "_", "symbols": ["_$ebnf$1"], "postprocess": () => null },
110
+ { "name": "__$ebnf$1", "symbols": ["WS"] },
111
+ { "name": "__$ebnf$1", "symbols": ["__$ebnf$1", "WS"], "postprocess": (d) => d[0].concat([d[1]]) },
112
+ { "name": "__", "symbols": ["__$ebnf$1"], "postprocess": () => null },
113
+ { "name": "_unit", "symbols": ["WS"] },
114
+ { "name": "WS", "symbols": [/[ \t]/] },
115
+ { "name": "notnl", "symbols": [/[^\n]/] }
116
+ ],
117
+ ParserStart: "File",
118
+ };
119
+ export default grammar;
@@ -0,0 +1,25 @@
1
+ import { InstancedMesh, Material } from "three";
2
+ import { Interpreter } from "../interpreter.js";
3
+ import { type GrassMaterialOptions } from "./material.js";
4
+ export type GrassOptions = GrassMaterialOptions & {
5
+ bladeSegments?: number;
6
+ /**
7
+ * Fade width in meters (preferred). If set, overrides edgeFadeFraction and makes
8
+ * the fade invariant to cell size. Default: 0.5 (meters) when not using edgeFadeFraction.
9
+ */
10
+ edgeFadeMeters?: number;
11
+ /**
12
+ * Minimum size scale (applied to both height and width) at the edge.
13
+ * Range: 0..1. Default: 0.25.
14
+ */
15
+ minSizeScale?: number;
16
+ /**
17
+ * Strength (0..1) of variation per meter.
18
+ * Default: 0.8.
19
+ */
20
+ variationStrength?: number;
21
+ };
22
+ export declare class GrassMesh extends InstancedMesh {
23
+ constructor(interpreter: Interpreter, material?: Material, opts?: GrassOptions);
24
+ }
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/grass/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,aAAa,EACb,QAAQ,EAMT,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAsB,KAAK,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAY9E,MAAM,MAAM,YAAY,GAAG,oBAAoB,GAAG;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF,qBAAa,SAAU,SAAQ,aAAa;gBAExC,WAAW,EAAE,WAAW,EACxB,QAAQ,GAAE,QAAkC,EAC5C,IAAI,GAAE,YAAiB;CAoO1B"}
@@ -0,0 +1,177 @@
1
+ import { Color, DoubleSide, InstancedMesh, Matrix4, MeshPhongMaterial, PlaneGeometry, Quaternion, Vector3, } from "three";
2
+ import { coerce, object, string } from "zod";
3
+ import { buildGrassMaterial } from "./material.js";
4
+ import { TilesGeometry } from "../tiles/geometry.js";
5
+ const grassSchema = object({
6
+ color: string().optional(),
7
+ height: coerce.number().optional(),
8
+ density: coerce.number().optional(),
9
+ });
10
+ const UpVector = new Vector3(0, 1, 0);
11
+ const colorHelper = new Color();
12
+ export class GrassMesh extends InstancedMesh {
13
+ constructor(interpreter, material = new MeshPhongMaterial(), opts = {}) {
14
+ material.side = DoubleSide;
15
+ const rows = interpreter.getRows();
16
+ const cols = interpreter.getCols();
17
+ const cellSize = interpreter.getCellSize();
18
+ const perCell = new Array(rows)
19
+ .fill(undefined)
20
+ .map(() => new Array(cols).fill(null));
21
+ let maxBlades = 0;
22
+ for (let z = 0; z < rows; z++) {
23
+ for (let x = 0; x < cols; x++) {
24
+ const grassParsed = interpreter
25
+ .getCalls([z, x], { grass: grassSchema })
26
+ .at(-1)?.[1];
27
+ if (!grassParsed)
28
+ continue;
29
+ const height = grassParsed.height ?? 0.5;
30
+ const density = Math.max(1, Math.floor(grassParsed.density ?? 100));
31
+ const subN = Math.ceil(Math.sqrt(density) * cellSize);
32
+ const width = 0.1;
33
+ const step = cellSize / subN;
34
+ const jitterAmp = step * 0.35;
35
+ // top surface stack index = total number of ground/ceiling - 1
36
+ const topStackIndex = Math.max(0, interpreter.countCalls([z, x], (c) => c.name === "ground" || c.name === "ceiling") - 1);
37
+ perCell[z][x] = {
38
+ subN,
39
+ jitterAmp,
40
+ height,
41
+ width,
42
+ topStackIndex,
43
+ color: grassParsed.color ?? 0x6c763d,
44
+ };
45
+ maxBlades += subN * subN;
46
+ }
47
+ }
48
+ // Compute neighbor flags
49
+ function hasGrassCell(z, x) {
50
+ return (!(z < 0 || x < 0 || z >= rows || x >= cols) && perCell[z][x] != null);
51
+ }
52
+ for (let z = 0; z < rows; z++) {
53
+ for (let x = 0; x < cols; x++) {
54
+ const cell = perCell[z][x];
55
+ if (!cell)
56
+ continue;
57
+ cell.neighbors = {
58
+ top: !hasGrassCell(z - 1, x), // north
59
+ right: !hasGrassCell(z, x + 1), // east
60
+ bottom: !hasGrassCell(z + 1, x), // south
61
+ left: !hasGrassCell(z, x - 1), // west
62
+ topLeft: !hasGrassCell(z - 1, x - 1),
63
+ topRight: !hasGrassCell(z - 1, x + 1),
64
+ bottomRight: !hasGrassCell(z + 1, x + 1),
65
+ bottomLeft: !hasGrassCell(z + 1, x - 1),
66
+ };
67
+ }
68
+ }
69
+ const baseSegments = Math.max(1, Math.floor(opts.bladeSegments ?? 3));
70
+ const geometry = new PlaneGeometry(1, 1, 1, baseSegments);
71
+ geometry.translate(0, 0.5, 0); // base at y=0
72
+ super(geometry, material, maxBlades);
73
+ buildGrassMaterial(this.material, opts);
74
+ const minSizeScale = Math.min(Math.max(opts.minSizeScale ?? 0.25, 0), 1);
75
+ const fadeWidthMeters = opts.edgeFadeMeters ?? 1.5;
76
+ const variationStrength = Math.min(Math.max(opts.variationStrength ?? 0.5, 0), 1);
77
+ const invFadeScale = fadeWidthMeters > 0 ? 1 / fadeWidthMeters : 0;
78
+ const invFade = (d) => fadeWidthMeters <= 0 ? 1 : Math.min(Math.max(d * invFadeScale, 0), 1);
79
+ let instanceIndex = 0;
80
+ const tmpPos = new Vector3();
81
+ const mat = new Matrix4();
82
+ const quat = new Quaternion();
83
+ const scale = new Vector3(1, 1, 1);
84
+ const tilesGeometry = interpreter.getAsset(TilesGeometry, "tilesGeometry");
85
+ // Single pass generation
86
+ for (let z = 0; z < rows; z++) {
87
+ for (let x = 0; x < cols; x++) {
88
+ const cell = perCell[z][x];
89
+ if (!cell)
90
+ continue;
91
+ const { subN, jitterAmp, height, width, topStackIndex, neighbors } = cell;
92
+ const nb = neighbors;
93
+ const step = cellSize / subN;
94
+ // Cell center
95
+ const cx = (x - cols / 2 + 0.5) * cellSize;
96
+ const cz = (z - rows / 2 + 0.5) * cellSize;
97
+ for (let gz = 0; gz < subN; gz++) {
98
+ for (let gx = 0; gx < subN; gx++) {
99
+ // Optimization: Check stochastic rejection early using simplified fade check
100
+ // Normalized coordinates [0,1]
101
+ const u = (gx + 0.5) / subN;
102
+ const v = (gz + 0.5) / subN;
103
+ // Distances to edges
104
+ const west = u * cellSize;
105
+ const east = (1 - u) * cellSize;
106
+ const north = v * cellSize;
107
+ const south = (1 - v) * cellSize;
108
+ let f = 1;
109
+ // Only compute relevant distances based on neighbors
110
+ if (nb.left)
111
+ f = Math.min(f, invFade(west));
112
+ if (nb.right)
113
+ f = Math.min(f, invFade(east));
114
+ if (nb.top)
115
+ f = Math.min(f, invFade(north));
116
+ if (nb.bottom)
117
+ f = Math.min(f, invFade(south));
118
+ if (f < 1) {
119
+ // Corner checks if edges didn't already clamp it to 0
120
+ // (Only strictly needed if corner is missing but edges are present?
121
+ // Actually, if left is missing, west is small.
122
+ // If left is present, west doesn't matter for 'left' check.
123
+ // Corner check is for when top AND left are present, but topLeft is missing.)
124
+ if (nb.topLeft)
125
+ f = Math.min(f, invFade(Math.hypot(west, north)));
126
+ if (nb.topRight)
127
+ f = Math.min(f, invFade(Math.hypot(east, north)));
128
+ if (nb.bottomRight)
129
+ f = Math.min(f, invFade(Math.hypot(east, south)));
130
+ if (nb.bottomLeft)
131
+ f = Math.min(f, invFade(Math.hypot(west, south)));
132
+ }
133
+ // Stochastic acceptance
134
+ // Using Math.random() is fast and provides white noise distribution (no patterns)
135
+ if (Math.random() >= f)
136
+ continue;
137
+ // --- Instance is accepted ---
138
+ // Position
139
+ const ox = -cellSize * 0.5 + (gx + 0.5) * step;
140
+ const oz = -cellSize * 0.5 + (gz + 0.5) * step;
141
+ const jx = (Math.random() * 2 - 1) * jitterAmp;
142
+ const jz = (Math.random() * 2 - 1) * jitterAmp;
143
+ const px = cx + ox + jx;
144
+ const pz = cz + oz + jz;
145
+ let baseY = 0;
146
+ if (tilesGeometry) {
147
+ baseY = tilesGeometry.getHeight(x, z, topStackIndex, px - cx, pz - cz);
148
+ }
149
+ tmpPos.set(px, baseY, pz);
150
+ // Scale
151
+ const sizeScale = minSizeScale + (1 - minSizeScale) * f;
152
+ // Random variation (-1 to 1)
153
+ const sizeVar = Math.random() * 2 - 1;
154
+ const heightJitter = 1 + sizeVar * variationStrength;
155
+ const widthJitter = 1 + sizeVar * (0.5 * variationStrength);
156
+ scale.set(width * sizeScale * widthJitter, height * sizeScale * heightJitter, 1);
157
+ // Rotation
158
+ quat.setFromAxisAngle(UpVector, (Math.random() - 0.5) * Math.PI * 2.0);
159
+ mat.compose(tmpPos, quat, scale);
160
+ this.setMatrixAt(instanceIndex, mat);
161
+ // Color
162
+ const colorVar = Math.random() * 2 - 1;
163
+ const colorScale = Math.max(0.7, Math.min(1.4, 1 + colorVar * 0.5 * variationStrength));
164
+ // 10x boost kept from original logic
165
+ this.setColorAt(instanceIndex, colorHelper.set(cell.color).multiplyScalar(colorScale * 10));
166
+ instanceIndex++;
167
+ }
168
+ }
169
+ }
170
+ }
171
+ // Update count to match actual generated blades
172
+ this.count = instanceIndex;
173
+ this.instanceMatrix.needsUpdate = true;
174
+ if (this.instanceColor)
175
+ this.instanceColor.needsUpdate = true;
176
+ }
177
+ }
@@ -0,0 +1,10 @@
1
+ import { Material } from "three";
2
+ export type GrassMaterialOptions = {
3
+ windStrength?: number;
4
+ windSpatialFreq?: number;
5
+ windTemporalFreq?: number;
6
+ baseCurveStrength?: number;
7
+ windCurveInfluence?: number;
8
+ };
9
+ export declare function buildGrassMaterial(material: Material, opts?: GrassMaterialOptions): void;
10
+ //# sourceMappingURL=material.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"material.d.ts","sourceRoot":"","sources":["../../src/grass/material.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEjC,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,GAAE,oBAAyB,GAC9B,IAAI,CAiGN"}
@@ -0,0 +1,80 @@
1
+ export function buildGrassMaterial(material, opts = {}) {
2
+ const windStrength = (opts.windStrength ?? 1.5).toFixed(6);
3
+ const windFreqX = (opts.windSpatialFreq ?? 1.5).toFixed(6);
4
+ const windFreqT = (opts.windTemporalFreq ?? 1.5).toFixed(6);
5
+ const baseCurveStrength = (opts.baseCurveStrength ?? 0.15).toFixed(6);
6
+ const windCurveInfluence = (opts.windCurveInfluence ?? 1).toFixed(6);
7
+ material.onBeforeCompile = (shader) => {
8
+ shader.uniforms.uTime = { value: 0 };
9
+ shader.vertexShader = shader.vertexShader
10
+ .replace("#include <common>", `#include <common>
11
+ varying float vH; // height along blade [0..1]
12
+ uniform float uTime;
13
+
14
+ // Simple hash and noise helpers
15
+ float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453123); }
16
+ float noise(vec2 p){
17
+ vec2 i = floor(p); vec2 f = fract(p);
18
+ float a = hash(i);
19
+ float b = hash(i + vec2(1.0, 0.0));
20
+ float c = hash(i + vec2(0.0, 1.0));
21
+ float d = hash(i + vec2(1.0, 1.0));
22
+ vec2 u = f*f*(3.0-2.0*f);
23
+ return mix(a, b, u.x) + (c - a)*u.y*(1.0 - u.x) + (d - b)*u.x*u.y;
24
+ }
25
+ `)
26
+ .replace("#include <defaultnormal_vertex>", `// Rotate object-space normal by the same forward bend (includes wind) before normalMatrix
27
+ float vHn = clamp( uv.y, 0.0, 1.0 );
28
+ vec3 iPos_n = (instanceMatrix)[3].xyz;
29
+ float n_n = noise(vec2(iPos_n.x * ${windFreqX}, iPos_n.z * ${windFreqX}) + vec2(uTime*${windFreqT}, 0.0));
30
+ float wind_n = ${windStrength} * (n_n - 0.5);
31
+ float baseCurve_n = vHn * ${baseCurveStrength};
32
+ float curve_n = baseCurve_n * (1.0 + ${windCurveInfluence} * wind_n);
33
+ float cn = cos(curve_n);
34
+ float sn = sin(curve_n);
35
+ objectNormal.yz = mat2(cn, -sn, sn, cn) * objectNormal.yz;
36
+
37
+ // Apply lateral sway to local object normal as well (independent of wind)
38
+ float lateral = (uv.x - 0.5);
39
+ float sway = 0.08 * lateral;
40
+ float cny = cos(sway);
41
+ float sny = sin(sway);
42
+ objectNormal.xz = mat2(cny, -sny, sny, cny) * objectNormal.xz;
43
+
44
+ #include <defaultnormal_vertex>
45
+ `)
46
+ .replace("#include <begin_vertex>", `#include <begin_vertex>
47
+ // Height fraction from UVs (plane geometry provides 0..1 along v)
48
+ vH = clamp( uv.y, 0.0, 1.0 );
49
+
50
+ // Non-linear taper: 1 at base, 0 at tip
51
+ float widthScale = 1.0 - vH * vH;
52
+ transformed.x *= widthScale;
53
+
54
+ // Wind: sample noise from instance world position (translation from instanceMatrix)
55
+ vec3 iPos = (instanceMatrix)[3].xyz;
56
+ float n = noise(vec2(iPos.x * ${windFreqX}, iPos.z * ${windFreqX}) + vec2(uTime*${windFreqT}, 0.0));
57
+ float wind = ${windStrength} * (n - 0.5);
58
+
59
+ // Always apply a gentle forward curve that increases toward the tip,
60
+ // and modulate it slightly by wind
61
+ float baseCurve = vH * ${baseCurveStrength};
62
+ float curve = baseCurve * (1.0 + ${windCurveInfluence} * wind);
63
+ float cw = cos(curve);
64
+ float sw = sin(curve);
65
+ transformed.yz = mat2(cw, -sw, sw, cw) * transformed.yz;
66
+ `);
67
+ // Fragment modifications: declare varyings and blend color towards tip color by height
68
+ shader.fragmentShader = shader.fragmentShader
69
+ .replace("#include <common>", `#include <common>
70
+ varying float vH;
71
+ `)
72
+ .replace("#include <color_fragment>", `#include <color_fragment>
73
+ // Darken blade downward (vH 0 -> darker, vH 1 -> base color)
74
+ diffuseColor.rgb *= mix(0.2, 1.0, vH);
75
+ `);
76
+ // Animate time
77
+ const old = shader.uniforms;
78
+ material.__grassTick = (t) => { old.uTime.value = t; };
79
+ };
80
+ }