@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,201 @@
1
+ import { DataTexture, RepeatWrapping, LinearMipMapLinearFilter, LinearFilter, RGBAFormat, RedFormat, UnsignedByteType, FloatType, Vector2, } from "three";
2
+ import FFT from "fft.js";
3
+ import seedrandom from "seedrandom";
4
+ export function createWaveHeightTexture(options = {}) {
5
+ const size = options.size ?? 256;
6
+ const seed = options.seed ?? 1337;
7
+ const wind = options.windDirection
8
+ ? options.windDirection.clone().normalize()
9
+ : new Vector2(1, 1).normalize();
10
+ const windSpeed = options.windSpeed ?? 10.0;
11
+ const alignment = options.alignment ?? 1.0;
12
+ const heightScale = options.heightScale ?? 1.0;
13
+ const rng = seedrandom(seed.toString());
14
+ // 1. Generate frequency domain data using Phillips spectrum
15
+ // Phillips spectrum constants
16
+ const g = 9.81;
17
+ const L = (windSpeed * windSpeed) / g;
18
+ const L2 = L * L;
19
+ const numPixels = size * size;
20
+ const data = new Float32Array(numPixels * 2); // Complex data [re, im, re, im...]
21
+ for (let y = 0; y < size; y++) {
22
+ for (let x = 0; x < size; x++) {
23
+ // Frequencies kx, kz
24
+ // Range -size/2 to size/2 corresponds to wavenumbers
25
+ let kx = x < size / 2 ? x : x - size;
26
+ let kz = y < size / 2 ? y : y - size;
27
+ // Scale to physical units (assuming patch size L_patch = size meters)
28
+ const patchSize = size;
29
+ kx = (2 * Math.PI * kx) / patchSize;
30
+ kz = (2 * Math.PI * kz) / patchSize;
31
+ const kLength = Math.sqrt(kx * kx + kz * kz);
32
+ if (kLength < 0.000001) {
33
+ data[(y * size + x) * 2] = 0;
34
+ data[(y * size + x) * 2 + 1] = 0;
35
+ continue;
36
+ }
37
+ const kDotW = (kx * wind.x + kz * wind.y) / kLength;
38
+ const k2 = kLength * kLength;
39
+ const k4 = k2 * k2;
40
+ // P(k) = A * exp(-1/(kL)^2) / k^4 * |k_hat dot w_hat|^2
41
+ // A is a global scale factor, we can fold it into heightScale or keep it 1.0
42
+ const A = 1000.0;
43
+ const suppression = Math.exp(-1 / (k2 * L2)) / k4;
44
+ const directional = Math.pow(Math.abs(kDotW), alignment * 2);
45
+ const Pk = A * suppression * directional;
46
+ // Generate random complex amplitude: h = (1/sqrt(2)) * (Gr + i*Gi) * sqrt(Pk)
47
+ // We use random Gaussian numbers for Gr, Gi
48
+ const hReal = gaussianRandom(rng) * Math.sqrt(Pk) * 0.707106;
49
+ const hImag = gaussianRandom(rng) * Math.sqrt(Pk) * 0.707106;
50
+ const idx = (y * size + x) * 2;
51
+ data[idx] = hReal;
52
+ data[idx + 1] = hImag;
53
+ }
54
+ }
55
+ // 2. Perform 2D Inverse FFT
56
+ const fft = new FFT(size);
57
+ const rowInput = new Float32Array(size * 2);
58
+ const rowOutput = new Float32Array(size * 2);
59
+ // Transposed output for column processing
60
+ const transposed = new Float32Array(numPixels * 2);
61
+ // Process Rows
62
+ for (let y = 0; y < size; y++) {
63
+ for (let x = 0; x < size; x++) {
64
+ const idx = (y * size + x) * 2;
65
+ rowInput[x * 2] = data[idx];
66
+ rowInput[x * 2 + 1] = data[idx + 1];
67
+ }
68
+ fft.inverseTransform(rowOutput, rowInput);
69
+ // Write to transposed buffer immediately for next step
70
+ // Transposed: (x, y) -> (y, x)
71
+ // Input row y, col x -> Output row x, col y
72
+ for (let x = 0; x < size; x++) {
73
+ const outIdx = (x * size + y) * 2;
74
+ transposed[outIdx] = rowOutput[x * 2];
75
+ transposed[outIdx + 1] = rowOutput[x * 2 + 1];
76
+ }
77
+ }
78
+ // Process Columns (which are now rows in 'transposed')
79
+ // Reuse data array for final result
80
+ for (let y = 0; y < size; y++) {
81
+ for (let x = 0; x < size; x++) {
82
+ const idx = (y * size + x) * 2;
83
+ rowInput[x * 2] = transposed[idx];
84
+ rowInput[x * 2 + 1] = transposed[idx + 1];
85
+ }
86
+ fft.inverseTransform(rowOutput, rowInput);
87
+ // Write back to 'data' or directly to heightData
88
+ // Note: This output is transposed relative to 'transposed', so it's back to (y, x) order
89
+ // But wait, we transposed once.
90
+ // If we process rows of the transposed matrix, we are processing columns of original.
91
+ // The output of this second pass is (y', x') where y' was x of original.
92
+ // So the result is transposed.
93
+ // Let's write to 'data' as (y, x) (where y corresponds to original col x, x to original row y)
94
+ for (let x = 0; x < size; x++) {
95
+ // We want to store it such that we can read it easily.
96
+ // Let's just store it in row-major order of the current iteration
97
+ const idx = (y * size + x) * 2;
98
+ data[idx] = rowOutput[x * 2];
99
+ data[idx + 1] = rowOutput[x * 2 + 1];
100
+ }
101
+ }
102
+ // Extract Real part (Height) and correct orientation
103
+ // 'data' now contains the 2D IFFT result, but it is Transposed because we did Row-Row pass where 2nd pass was on transposed data.
104
+ // Original: Rows -> Transpose -> Rows (Columns of orig) -> Result (Transposed)
105
+ // So data[y * size + x] corresponds to height at (x, y) of the original grid.
106
+ const heightData = new Float32Array(numPixels);
107
+ for (let y = 0; y < size; y++) {
108
+ for (let x = 0; x < size; x++) {
109
+ // To get (y, x) of the final image, we read (x, y) from 'data'
110
+ // data index: x * size + y
111
+ const idx = (x * size + y) * 2;
112
+ const val = data[idx];
113
+ // Apply sign correction for FFT shift if needed?
114
+ // Phillips spectrum usually doesn't require centering if k is handled correctly.
115
+ // However, standard FFT implementation might need normalization?
116
+ // fft.js usually doesn't normalize inverse transform by 1/N?
117
+ // Checking docs: "The inverse transform is unscaled."
118
+ // So we need to divide by size*size?
119
+ // Let's check if user provided 'heightScale' handles it.
120
+ // Usually we need to normalize.
121
+ // But let's just stick to the visual scaling for now or divide by size if it's huge.
122
+ // With size=256, val might be 65536 times larger.
123
+ // Let's normalize by numPixels to be safe mathematically, then apply heightScale.
124
+ // Update: fft.js inverseTransform already normalizes by 1/N.
125
+ // Since we do two passes (rows then cols), it normalizes by 1/N_rows * 1/N_cols = 1/numPixels.
126
+ // So 'val' is already normalized. dividing by numPixels again makes it tiny.
127
+ heightData[y * size + x] = val * heightScale;
128
+ }
129
+ }
130
+ const tex = new DataTexture(heightData, size, size, RedFormat, FloatType);
131
+ tex.wrapS = RepeatWrapping;
132
+ tex.wrapT = RepeatWrapping;
133
+ tex.minFilter = LinearMipMapLinearFilter;
134
+ tex.magFilter = LinearFilter;
135
+ tex.generateMipmaps = true;
136
+ tex.needsUpdate = true;
137
+ return tex;
138
+ }
139
+ export function createWaveNormalTexture(heightTexture, options = {}) {
140
+ const size = heightTexture.image.width;
141
+ const heightData = heightTexture.image.data;
142
+ // Generate Normal Map
143
+ const normalData = new Uint8Array(size * size * 4);
144
+ const strength = options.strength ?? 2.0; // Normal map strength
145
+ for (let y = 0; y < size; y++) {
146
+ for (let x = 0; x < size; x++) {
147
+ const xp = (x - 1 + size) % size;
148
+ const xn = (x + 1) % size;
149
+ const yp = (y - 1 + size) % size;
150
+ const yn = (y + 1) % size;
151
+ const hx0 = heightData[y * size + xp];
152
+ const hx1 = heightData[y * size + xn];
153
+ const hy0 = heightData[yp * size + x];
154
+ const hy1 = heightData[yn * size + x];
155
+ // Central difference
156
+ // The texture is wrapped, so using neighbors is safe.
157
+ // The gradient here is dz/dx and dz/dy.
158
+ // If displacementScale in material is S, then actual height H = h * S.
159
+ // Gradient is (H(x+1)-H(x-1))/2 = (h(x+1)-h(x-1)) * S / 2.
160
+ // 'strength' here acts as the 'S / 2' factor relative to the '1 unit' pixel distance.
161
+ // If the user uses displacementScale=10, they probably want strong normals.
162
+ const dx = (hx1 - hx0) * strength;
163
+ const dy = (hy1 - hy0) * strength;
164
+ // Normal Vector construction
165
+ // Tangent Space:
166
+ // T = (1, 0, dx)
167
+ // B = (0, 1, dy)
168
+ // N = T x B = (-dx, -dy, 1)
169
+ // Normalized: n = N / |N|
170
+ // RGB Packing: 0.5 * n + 0.5
171
+ let nx = -dx;
172
+ let ny = -dy;
173
+ let nz = 1.0;
174
+ const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
175
+ nx /= len;
176
+ ny /= len;
177
+ nz /= len;
178
+ const ptr = (y * size + x) * 4;
179
+ normalData[ptr] = ((nx * 0.5 + 0.5) * 255) | 0;
180
+ normalData[ptr + 1] = ((ny * 0.5 + 0.5) * 255) | 0;
181
+ normalData[ptr + 2] = ((nz * 0.5 + 0.5) * 255) | 0;
182
+ normalData[ptr + 3] = 255;
183
+ }
184
+ }
185
+ const tex = new DataTexture(normalData, size, size, RGBAFormat, UnsignedByteType);
186
+ tex.wrapS = RepeatWrapping;
187
+ tex.wrapT = RepeatWrapping;
188
+ tex.minFilter = LinearMipMapLinearFilter;
189
+ tex.magFilter = LinearFilter;
190
+ tex.generateMipmaps = true;
191
+ tex.needsUpdate = true;
192
+ return tex;
193
+ }
194
+ function gaussianRandom(rng) {
195
+ let u = 0, v = 0;
196
+ while (u === 0)
197
+ u = rng();
198
+ while (v === 0)
199
+ v = rng();
200
+ return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
201
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@drawcall/charta",
3
+ "version": "0.0.0",
4
+ "author": "Bela Bohlender",
5
+ "license": "SEE LICENSE IN LICENSE",
6
+ "homepage": "https://drawcall.ai",
7
+ "keywords": [
8
+ "drawcall.ai"
9
+ ],
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "main": "dist/index.js",
14
+ "type": "module",
15
+ "devDependencies": {
16
+ "@types/imurmurhash": "^0.1.4",
17
+ "@types/nearley": "^2.11.5",
18
+ "@types/seedrandom": "^3.0.8",
19
+ "moo": "^0.5.2",
20
+ "typescript": "^5.5.0",
21
+ "vitest": "^4.0.0"
22
+ },
23
+ "dependencies": {
24
+ "nearley": "^2.20.1",
25
+ "fft.js": "^4.0.4",
26
+ "imurmurhash": "^0.1.4",
27
+ "seedrandom": "^3.0.5",
28
+ "zod": "^4.1.12"
29
+ },
30
+ "peerDependencies": {
31
+ "three": ">=0.157.0"
32
+ },
33
+ "scripts": {
34
+ "test": "vitest run",
35
+ "test:watch": "vitest",
36
+ "build": "tsc -p tsconfig.json",
37
+ "generate": "nearleyc --module es src/grammar.ne -o src/grammar.ts && node -e \"const fs=require('fs'); const p='src/grammar.ts'; const s=fs.readFileSync(p,'utf8'); fs.writeFileSync(p, '// @ts-nocheck\\n'+s);\""
38
+ }
39
+ }