@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.
- package/LICENSE +7 -0
- package/dist/assets/loader.d.ts +21 -0
- package/dist/assets/loader.d.ts.map +1 -0
- package/dist/assets/loader.js +113 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +27 -0
- package/dist/grammar.d.ts +29 -0
- package/dist/grammar.d.ts.map +1 -0
- package/dist/grammar.js +119 -0
- package/dist/grass/index.d.ts +25 -0
- package/dist/grass/index.d.ts.map +1 -0
- package/dist/grass/index.js +177 -0
- package/dist/grass/material.d.ts +10 -0
- package/dist/grass/material.d.ts.map +1 -0
- package/dist/grass/material.js +80 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/interpreter.d.ts +47 -0
- package/dist/interpreter.d.ts.map +1 -0
- package/dist/interpreter.js +226 -0
- package/dist/locations.d.ts +16 -0
- package/dist/locations.d.ts.map +1 -0
- package/dist/locations.js +58 -0
- package/dist/parser.d.ts +9 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +47 -0
- package/dist/pillars/index.d.ts +7 -0
- package/dist/pillars/index.d.ts.map +1 -0
- package/dist/pillars/index.js +154 -0
- package/dist/pillars/material.d.ts +3 -0
- package/dist/pillars/material.d.ts.map +1 -0
- package/dist/pillars/material.js +43 -0
- package/dist/place/index.d.ts +37 -0
- package/dist/place/index.d.ts.map +1 -0
- package/dist/place/index.js +216 -0
- package/dist/tiles/geometry.d.ts +46 -0
- package/dist/tiles/geometry.d.ts.map +1 -0
- package/dist/tiles/geometry.js +463 -0
- package/dist/tiles/index.d.ts +18 -0
- package/dist/tiles/index.d.ts.map +1 -0
- package/dist/tiles/index.js +121 -0
- package/dist/tiles/material.d.ts +6 -0
- package/dist/tiles/material.d.ts.map +1 -0
- package/dist/tiles/material.js +88 -0
- package/dist/utils/instanced-mesh-group.d.ts +17 -0
- package/dist/utils/instanced-mesh-group.d.ts.map +1 -0
- package/dist/utils/instanced-mesh-group.js +59 -0
- package/dist/utils/random.d.ts +4 -0
- package/dist/utils/random.d.ts.map +1 -0
- package/dist/utils/random.js +19 -0
- package/dist/utils/texture.d.ts +3 -0
- package/dist/utils/texture.d.ts.map +1 -0
- package/dist/utils/texture.js +30 -0
- package/dist/walls/index.d.ts +87 -0
- package/dist/walls/index.d.ts.map +1 -0
- package/dist/walls/index.js +376 -0
- package/dist/walls/material.d.ts +3 -0
- package/dist/walls/material.d.ts.map +1 -0
- package/dist/walls/material.js +67 -0
- package/dist/water/index.d.ts +10 -0
- package/dist/water/index.d.ts.map +1 -0
- package/dist/water/index.js +46 -0
- package/dist/water/material.d.ts +5 -0
- package/dist/water/material.d.ts.map +1 -0
- package/dist/water/material.js +46 -0
- package/dist/water/texture.d.ts +15 -0
- package/dist/water/texture.d.ts.map +1 -0
- package/dist/water/texture.js +201 -0
- 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
|
+
}
|