@weft-js/plugin-surface-keel 0.1.0 → 0.1.3
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/README.md +312 -3
- package/package.json +12 -3
- package/src/index.d.ts +5 -1
- package/src/index.mjs +20 -5
- package/src/runtime.mjs +6 -2
- package/vendor/README.md +7 -4
- package/vendor/keel-render/core-primitives.mjs +478 -0
- package/vendor/keel-render/effects-world.mjs +439 -0
- package/vendor/keel-render/indexed-render.mjs +516 -0
- package/vendor/keel-render/pixel-raster.mjs +512 -0
- package/vendor/keel-render/render-passes.mjs +530 -0
- package/vendor/keel-render/renderer-api.mjs +121 -0
- package/vendor/keel-render.mjs +1 -2268
- package/vendor/keel-theme/codec-runtime.mjs +549 -0
- package/vendor/keel-theme/collection-codecs.mjs +360 -0
- package/vendor/keel-theme/color-font.mjs +471 -0
- package/vendor/keel-theme/glyph-theme.mjs +529 -0
- package/vendor/keel-theme/icon-schema.mjs +540 -0
- package/vendor/keel-theme/scalar-codecs.mjs +535 -0
- package/vendor/keel-theme/schema-codec.mjs +515 -0
- package/vendor/keel-theme/schema-theme.mjs +533 -0
- package/vendor/keel-theme.mjs +1 -3571
package/vendor/keel-render.mjs
CHANGED
|
@@ -1,2268 +1 @@
|
|
|
1
|
-
|
|
2
|
-
var F64 = new Float64Array(1);
|
|
3
|
-
var U32 = new Uint32Array(F64.buffer);
|
|
4
|
-
F64[0] = 1;
|
|
5
|
-
var HI = U32[1] === 1072693248 ? 1 : 0;
|
|
6
|
-
var LO = 1 - HI;
|
|
7
|
-
function dhypot(a, b, c) {
|
|
8
|
-
const x = Math.abs(a);
|
|
9
|
-
const y = Math.abs(b);
|
|
10
|
-
const three = c !== void 0;
|
|
11
|
-
const z = three ? Math.abs(c) : 0;
|
|
12
|
-
if (x === Infinity || y === Infinity || z === Infinity) return Infinity;
|
|
13
|
-
if (x !== x || y !== y || z !== z) return NaN;
|
|
14
|
-
let max = x > y ? x : y;
|
|
15
|
-
if (z > max) max = z;
|
|
16
|
-
if (max === 0) return 0;
|
|
17
|
-
let n = x / max;
|
|
18
|
-
let sum = n * n;
|
|
19
|
-
let comp = 0;
|
|
20
|
-
n = y / max;
|
|
21
|
-
let summand = n * n - comp;
|
|
22
|
-
let pre = sum + summand;
|
|
23
|
-
comp = pre - sum - summand;
|
|
24
|
-
sum = pre;
|
|
25
|
-
if (three) {
|
|
26
|
-
n = z / max;
|
|
27
|
-
summand = n * n - comp;
|
|
28
|
-
pre = sum + summand;
|
|
29
|
-
sum = pre;
|
|
30
|
-
}
|
|
31
|
-
return Math.sqrt(sum) * max;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ../../../../../../keel-engine/packages/core/src/frame.ts
|
|
35
|
-
var FRONT = Object.freeze([0, 0, 1]);
|
|
36
|
-
var RIGHT = Object.freeze([1, 0, 0]);
|
|
37
|
-
var UP = Object.freeze([0, 1, 0]);
|
|
38
|
-
function cameraBasis(eye, target) {
|
|
39
|
-
let f = [target[0] - eye[0], target[1] - eye[1], target[2] - eye[2]];
|
|
40
|
-
const fl = dhypot(f[0], f[1], f[2]) || 1;
|
|
41
|
-
f = [f[0] / fl, f[1] / fl, f[2] / fl];
|
|
42
|
-
let r = [f[2], 0, -f[0]];
|
|
43
|
-
const rl = dhypot(r[0], r[1], r[2]) || 1;
|
|
44
|
-
r = rl < 1e-6 ? [1, 0, 0] : [r[0] / rl, r[1] / rl, r[2] / rl];
|
|
45
|
-
const u = [f[1] * r[2] - f[2] * r[1], f[2] * r[0] - f[0] * r[2], f[0] * r[1] - f[1] * r[0]];
|
|
46
|
-
return { forward: f, right: r, up: u };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ../../../../../../keel-engine/packages/core/src/math.ts
|
|
50
|
-
var TAU = Math.PI * 2;
|
|
51
|
-
var fract = (v) => v - Math.floor(v);
|
|
52
|
-
var tri = (v) => 1 - Math.abs(2 * fract(v) - 1);
|
|
53
|
-
|
|
54
|
-
// ../../../../../../keel-engine/packages/core/src/curl.ts
|
|
55
|
-
var FLOW_PERIOD = 4096;
|
|
56
|
-
var FLOW_SPEED = 0.35;
|
|
57
|
-
var FLOW_R = FLOW_PERIOD * FLOW_SPEED / (2 * Math.PI);
|
|
58
|
-
var W = new Float64Array(8);
|
|
59
|
-
var IO = new Float64Array(7);
|
|
60
|
-
var CURL_GLSL = `
|
|
61
|
-
float curlLat(ivec3 i, uint s) {
|
|
62
|
-
uint h = (uint(i.x) * 0x8da6b343u) ^ (uint(i.y) * 0xd8163841u) ^ (uint(i.z) * 0xcb1ab31fu) ^ s;
|
|
63
|
-
h ^= h >> 16; h *= 0x7feb352du; h ^= h >> 15; h *= 0x846ca68bu; h ^= h >> 16;
|
|
64
|
-
return float(h) / 2147483647.5 - 1.0;
|
|
65
|
-
}
|
|
66
|
-
vec3 curlGrad(vec3 x, uint s) {
|
|
67
|
-
vec3 fi = floor(x); ivec3 i = ivec3(fi); vec3 f = x - fi;
|
|
68
|
-
vec3 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
|
|
69
|
-
vec3 du = 30.0 * f * f * (f * (f - 2.0) + 1.0);
|
|
70
|
-
float a = curlLat(i, s), b = curlLat(i + ivec3(1, 0, 0), s), c = curlLat(i + ivec3(0, 1, 0), s), d = curlLat(i + ivec3(1, 1, 0), s);
|
|
71
|
-
float e = curlLat(i + ivec3(0, 0, 1), s), ff = curlLat(i + ivec3(1, 0, 1), s), g = curlLat(i + ivec3(0, 1, 1), s), h = curlLat(i + ivec3(1, 1, 1), s);
|
|
72
|
-
float k1 = b - a, k2 = c - a, k3 = e - a, k4 = a - b - c + d, k5 = a - c - e + g, k6 = a - b - e + ff, k7 = -a + b + c - d + e - ff - g + h;
|
|
73
|
-
return du * vec3(k1 + k4 * u.y + k6 * u.z + k7 * u.y * u.z, k2 + k5 * u.z + k4 * u.x + k7 * u.z * u.x, k3 + k6 * u.x + k5 * u.y + k7 * u.x * u.y);
|
|
74
|
-
}
|
|
75
|
-
vec3 curlNoiseN(vec3 p, float t, int octaves) {
|
|
76
|
-
float th = t / ${FLOW_PERIOD.toFixed(1)} * 6.283185307;
|
|
77
|
-
p += vec3(${FLOW_R.toFixed(4)} * cos(th), 0.0, ${FLOW_R.toFixed(4)} * sin(th));
|
|
78
|
-
vec3 a = vec3(0.0), b = vec3(0.0);
|
|
79
|
-
float f = 1.0, w = 1.0;
|
|
80
|
-
for (int k = 0; k < 2; k++) {
|
|
81
|
-
if (k >= octaves) break;
|
|
82
|
-
a += curlGrad(p * f, 0x2545f491u + uint(k)) * f * w;
|
|
83
|
-
b += curlGrad(p * f + vec3(31.4, 17.7, 5.9), 0x68e31da4u + uint(k)) * f * w;
|
|
84
|
-
f *= 2.03; w *= 0.5;
|
|
85
|
-
}
|
|
86
|
-
return cross(a, b) * 0.25;
|
|
87
|
-
}
|
|
88
|
-
vec3 curlNoise(vec3 p, float t) { return curlNoiseN(p, t, 2); }`;
|
|
89
|
-
|
|
90
|
-
// ../../../../../../keel-engine/packages/core/src/palette.ts
|
|
91
|
-
function ramp(S, hues, extra = {}) {
|
|
92
|
-
return {
|
|
93
|
-
hues,
|
|
94
|
-
chroma: S.between(0.11, 0.19),
|
|
95
|
-
lift: S.between(0.04, 0.09),
|
|
96
|
-
top: S.between(0.94, 0.985),
|
|
97
|
-
gamma: S.between(1, 1.35),
|
|
98
|
-
shift: S.between(6, 20),
|
|
99
|
-
...extra
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
var accentRamp = (S, hue, extra = {}) => ({
|
|
103
|
-
hues: [hue - 6, hue + 6],
|
|
104
|
-
chroma: S.between(0.15, 0.23),
|
|
105
|
-
lift: 0.32,
|
|
106
|
-
top: 0.96,
|
|
107
|
-
gamma: 0.9,
|
|
108
|
-
shift: 4,
|
|
109
|
-
...extra
|
|
110
|
-
});
|
|
111
|
-
var SCHEMES = [
|
|
112
|
-
["Monochrome", 3, (S, h) => ({
|
|
113
|
-
key: ramp(S, [h - 4, h + 4]),
|
|
114
|
-
accent: accentRamp(S, h + S.between(-10, 10), { chroma: S.between(0.18, 0.25) })
|
|
115
|
-
})],
|
|
116
|
-
["Nocturne", 9, (S, h) => ({
|
|
117
|
-
key: ramp(S, [h - 9, h + 9]),
|
|
118
|
-
accent: accentRamp(S, h + 180 + S.between(-35, 35))
|
|
119
|
-
})],
|
|
120
|
-
["Duotone", 5, (S, h) => {
|
|
121
|
-
const d = S.pick([-1, 1]) * S.between(45, 85);
|
|
122
|
-
return { key: ramp(S, [h - d / 2, h + d / 2]), accent: accentRamp(S, h + 180 + S.between(-25, 25)) };
|
|
123
|
-
}],
|
|
124
|
-
["Split", 3, (S, h) => ({
|
|
125
|
-
key: ramp(S, [h - 12, h + 12]),
|
|
126
|
-
accent: accentRamp(S, h + S.pick([-1, 1]) * S.between(125, 155))
|
|
127
|
-
})],
|
|
128
|
-
["Triad", 3, (S, h) => {
|
|
129
|
-
const dir = S.pick([-1, 1]);
|
|
130
|
-
return { key: ramp(S, [h, h + dir * 120]), accent: accentRamp(S, h - dir * 120 + S.between(-15, 15)) };
|
|
131
|
-
}],
|
|
132
|
-
["Two Worlds", 7.5, (S, h) => {
|
|
133
|
-
const inside = h + 180 + S.between(-40, 40);
|
|
134
|
-
return {
|
|
135
|
-
outside: ramp(S, [h - 10, h + 10], { chroma: S.between(0.12, 0.2), top: 0.9 }),
|
|
136
|
-
key: ramp(S, [inside - 12, inside + 12]),
|
|
137
|
-
accent: accentRamp(S, inside + S.pick([-1, 1]) * S.between(25, 60))
|
|
138
|
-
};
|
|
139
|
-
}],
|
|
140
|
-
["Neon Noir", 2, (S, h) => ({
|
|
141
|
-
// Near-neutral room, one vivid ink. Contrast by placement, not by volume.
|
|
142
|
-
key: ramp(S, [h - 5, h + 5], { chroma: S.between(0.025, 0.05), shift: 4 }),
|
|
143
|
-
accent: accentRamp(S, h + S.pick([0, 150, 180, 210]), { chroma: 0.3, lift: 0.4 })
|
|
144
|
-
})],
|
|
145
|
-
["Spectrum", 1.5, (S, h) => {
|
|
146
|
-
const span = S.pick([-1, 1]) * S.between(150, 220);
|
|
147
|
-
return {
|
|
148
|
-
key: ramp(S, [h, h + span / 3, h + span * 2 / 3, h + span], { chroma: S.between(0.13, 0.18) }),
|
|
149
|
-
accent: accentRamp(S, h + span / 2 + 180)
|
|
150
|
-
};
|
|
151
|
-
}],
|
|
152
|
-
["Prism", 0.6, (S, h) => ({
|
|
153
|
-
key: ramp(S, [h, h + 90, h + 180, h + 270, h + 360], { chroma: S.between(0.13, 0.17), shift: 0 }),
|
|
154
|
-
accent: accentRamp(S, h + 45, { chroma: 0.12 })
|
|
155
|
-
})]
|
|
156
|
-
];
|
|
157
|
-
var SCHEME_NAMES = SCHEMES.map(([n]) => n);
|
|
158
|
-
|
|
159
|
-
// ../../../../../../keel-engine/packages/core/src/look.ts
|
|
160
|
-
var DRAWS = new Float64Array(16);
|
|
161
|
-
var WEIGHTS = [1, 0.8, 0.5];
|
|
162
|
-
var W_NORM = Math.sqrt(WEIGHTS.reduce((a, b) => a + b, 0));
|
|
163
|
-
|
|
164
|
-
// ../../../../../../keel-engine/packages/core/src/dither.ts
|
|
165
|
-
var BAYER2 = [0, 2, 3, 1];
|
|
166
|
-
var BAYER4 = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5];
|
|
167
|
-
var BAYER8 = (() => {
|
|
168
|
-
const out = new Array(64);
|
|
169
|
-
for (let y = 0; y < 8; y += 1) {
|
|
170
|
-
for (let x = 0; x < 8; x += 1) {
|
|
171
|
-
const q = BAYER4[(y & 3) * 4 + (x & 3)];
|
|
172
|
-
const b = BAYER2[(y >> 2) * 2 + (x >> 2)];
|
|
173
|
-
out[y * 8 + x] = q * 4 + b;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
return out;
|
|
177
|
-
})();
|
|
178
|
-
var b2 = (i) => BAYER2[i];
|
|
179
|
-
var b4 = (i) => BAYER4[i];
|
|
180
|
-
var b8 = (i) => BAYER8[i];
|
|
181
|
-
function equalize(shape, w, h, tie = (x, y) => b8((y & 7) * 8 + (x & 7))) {
|
|
182
|
-
const cells = [];
|
|
183
|
-
for (let y = 0; y < h; y += 1) for (let x = 0; x < w; x += 1) cells.push([Math.round(shape(x, y) * 1e6), tie(x, y), y * w + x]);
|
|
184
|
-
cells.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
|
|
185
|
-
const table = new Float32Array(w * h);
|
|
186
|
-
cells.forEach(([, , k], rank) => {
|
|
187
|
-
table[k] = (rank + 0.5) / cells.length;
|
|
188
|
-
});
|
|
189
|
-
return (x, y) => table[(y % h + h) % h * w + (x % w + w) % w];
|
|
190
|
-
}
|
|
191
|
-
var ALONG = [0, 4, 2, 6, 1, 5, 3, 7];
|
|
192
|
-
var along = (x) => ALONG[x & 7];
|
|
193
|
-
function dotScreen([ax, ay], [bx, by], w, h) {
|
|
194
|
-
const det = ax * by - ay * bx;
|
|
195
|
-
const coords = (x, y) => [(x * by - y * bx) / det, (y * ax - x * ay) / det];
|
|
196
|
-
const shape = (x, y) => {
|
|
197
|
-
const [u, v] = coords(x, y);
|
|
198
|
-
return -(Math.cos(2 * Math.PI * u) + Math.cos(2 * Math.PI * v));
|
|
199
|
-
};
|
|
200
|
-
const tie = (x, y) => {
|
|
201
|
-
const [u, v] = coords(x, y);
|
|
202
|
-
const [i, j] = [Math.round(u), Math.round(v)];
|
|
203
|
-
const dx = x - (i * ax + j * bx);
|
|
204
|
-
const dy = y - (i * ay + j * by);
|
|
205
|
-
const oct = Math.floor((Math.atan2(dy, dx) + 2 * Math.PI) % (2 * Math.PI) / (Math.PI / 4) + 1e-9) & 7;
|
|
206
|
-
return ALONG[oct];
|
|
207
|
-
};
|
|
208
|
-
return equalize(shape, w, h, tie);
|
|
209
|
-
}
|
|
210
|
-
var BLUE_NOISE = (() => {
|
|
211
|
-
const s = atob("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");
|
|
212
|
-
const t = new Uint8Array(s.length);
|
|
213
|
-
for (let i = 0; i < s.length; i += 1) t[i] = s.charCodeAt(i);
|
|
214
|
-
return t;
|
|
215
|
-
})();
|
|
216
|
-
var SCREENS = {
|
|
217
|
-
bayer2: { name: "Bayer 2", at: (x, y) => (b2((y & 1) * 2 + (x & 1)) + 0.5) / 4 },
|
|
218
|
-
bayer4: { name: "Bayer 4", at: (x, y) => (b4((y & 3) * 4 + (x & 3)) + 0.5) / 16 },
|
|
219
|
-
bayer8: { name: "Bayer 8", at: (x, y) => (b8((y & 7) * 8 + (x & 7)) + 0.5) / 64 },
|
|
220
|
-
chunky: { name: "Chunky", at: (x, y) => (b4((y >> 1 & 3) * 4 + (x >> 1 & 3)) + 0.5) / 16 },
|
|
221
|
-
// (As fine as the old screen: a dot every 18 pixels, now truly at 45°.)
|
|
222
|
-
halftone: { name: "Halftone", at: dotScreen([3, 3], [3, -3], 6, 6) },
|
|
223
|
-
// (The coarse dot keeps its nine flat levels for now: equalized, at 26.6°
|
|
224
|
-
// or square, its dots read as blotches to every critic. The owner's call.)
|
|
225
|
-
coarseDot: { name: "Coarse Dot", at: (x, y) => (Math.cos((x + y) / 6 * Math.PI) * Math.cos((x - y) / 6 * Math.PI) + 1) * 0.5 },
|
|
226
|
-
// (Line screen, crosshatch, checker and weave keep their few flat levels
|
|
227
|
-
// for now: equalized, a night sky's gradient turns to dashes, hatching lies
|
|
228
|
-
// across the objects and a checker becomes a lattice of specks -- and blind
|
|
229
|
-
// critics preferred the flat fills every time. The owner's call.)
|
|
230
|
-
lines: { name: "Line Screen", at: (x, y) => tri(y / 3 + 0.5) * 0.9 + b2((y & 1) * 2 + (x & 1)) / 4 * 0.1 },
|
|
231
|
-
diagonal: { name: "Diagonal", at: equalize((x, y) => [3, 1, 0, 2][x + y & 3], 8, 8, (x, y) => along(x + 4 * (x + y >> 2))) },
|
|
232
|
-
hatch: { name: "Crosshatch", at: (x, y) => (tri((x + y) / 4) + tri((x - y) / 4)) * 0.5 },
|
|
233
|
-
stipple: { name: "Stipple", at: (x, y) => (BLUE_NOISE[(y & 63) * 64 + (x & 63)] + 0.5) / 256 },
|
|
234
|
-
ign: { name: "Interleaved", at: (x, y) => fract(52.9829189 * fract(0.06711056 * x + 583715e-8 * y)) },
|
|
235
|
-
checker: { name: "Checker", at: (x, y) => x + y & 1 ? 0.3 : 0.7 },
|
|
236
|
-
weave: { name: "Weave", at: (x, y) => ((x >> 1) + (y >> 1) & 1 ? tri(x / 4) : tri(y / 4)) * 0.94 + 0.03 }
|
|
237
|
-
};
|
|
238
|
-
var SCREEN_IDS = Object.keys(SCREENS);
|
|
239
|
-
function measureScreen(at, T = 24) {
|
|
240
|
-
const vals = [];
|
|
241
|
-
for (let y = 0; y < T; y += 1) for (let x = 0; x < T; x += 1) vals.push(at(x, y));
|
|
242
|
-
const levels = new Set(vals.map((v) => v.toFixed(4))).size;
|
|
243
|
-
const bins = new Array(16).fill(0);
|
|
244
|
-
for (const v of vals) {
|
|
245
|
-
const b = Math.min(15, Math.floor(v * 16));
|
|
246
|
-
bins[b] = bins[b] + 1;
|
|
247
|
-
}
|
|
248
|
-
const maxBin = Math.max(...bins) / vals.length;
|
|
249
|
-
const mean = vals.reduce((a, v) => a + v, 0) / vals.length;
|
|
250
|
-
const varr = vals.reduce((a, v) => a + (v - mean) ** 2, 0) / vals.length || 1;
|
|
251
|
-
const corr = (dx, dy) => {
|
|
252
|
-
let acc = 0;
|
|
253
|
-
let n = 0;
|
|
254
|
-
for (let y = 0; y < T - 8; y += 1) for (let x = 8; x < T - 8; x += 1) {
|
|
255
|
-
acc += (at(x, y) - mean) * (at(x + dx, y + dy) - mean);
|
|
256
|
-
n += 1;
|
|
257
|
-
}
|
|
258
|
-
return acc / n / varr;
|
|
259
|
-
};
|
|
260
|
-
const shifts = [];
|
|
261
|
-
for (let dy = 0; dy <= 8; dy += 1) for (let dx = -8; dx <= 8; dx += 1) if (dy > 0 || dx > 0) shifts.push({ r: corr(dx, dy), dx, dy });
|
|
262
|
-
const rMax = Math.max(...shifts.map((s) => s.r));
|
|
263
|
-
const best = shifts.filter((s) => s.r > rMax - 0.02).sort((a, b) => Math.hypot(a.dx, a.dy) - Math.hypot(b.dx, b.dy))[0];
|
|
264
|
-
const angle = (Math.atan2(best.dy, best.dx) * 180 / Math.PI + 180) % 180;
|
|
265
|
-
const unit = [[0, 1, 0], [45, 1, 1], [90, 0, 1], [135, -1, 1]].map(([a, dx, dy]) => [a, corr(dx, dy)]);
|
|
266
|
-
const top = Math.max(...unit.map(([, r]) => r));
|
|
267
|
-
return {
|
|
268
|
-
levels,
|
|
269
|
-
maxBin,
|
|
270
|
-
period: +Math.hypot(best.dx, best.dy).toFixed(2),
|
|
271
|
-
angle: +angle.toFixed(1),
|
|
272
|
-
r: +best.r.toFixed(2),
|
|
273
|
-
// (Every direction within a hair of the smoothest counts: a crosshatch runs both diagonals.)
|
|
274
|
-
lines: unit.filter(([, r]) => r > top - 0.02).map(([a]) => a)
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
var SCREEN_GEOM = Object.fromEntries(SCREEN_IDS.map((id) => {
|
|
278
|
-
const m = measureScreen(SCREENS[id].at);
|
|
279
|
-
return [id, { period: m.period, angle: m.angle, r: m.r, lines: m.lines }];
|
|
280
|
-
}));
|
|
281
|
-
var SCREEN_KIND = {
|
|
282
|
-
bayer2: ["ordered", 1, "grid"],
|
|
283
|
-
bayer4: ["ordered", 1, "grid"],
|
|
284
|
-
bayer8: ["ordered", 1, "grid"],
|
|
285
|
-
chunky: ["ordered", 3, "grid"],
|
|
286
|
-
halftone: ["dot", 2, "diag"],
|
|
287
|
-
coarseDot: ["dot", 3, "grid"],
|
|
288
|
-
lines: ["line", 2, "h"],
|
|
289
|
-
diagonal: ["line", 2, "d"],
|
|
290
|
-
hatch: ["line", 2, "x"],
|
|
291
|
-
stipple: ["noise", 1, "none"],
|
|
292
|
-
ign: ["noise", 1, "ign"],
|
|
293
|
-
checker: ["pattern", 3, "diag"],
|
|
294
|
-
weave: ["pattern", 3, "grid"]
|
|
295
|
-
};
|
|
296
|
-
var TARGET_BANDS = [["tiny", 48], ["small", 128], ["large", Infinity]];
|
|
297
|
-
var TARGET_SCREENS = {
|
|
298
|
-
ordered: { tiny: "bayer2", small: "bayer4", large: "bayer8" },
|
|
299
|
-
dot: { tiny: "bayer2", small: "halftone", large: "halftone" },
|
|
300
|
-
line: { tiny: "bayer2", small: "lines", large: "hatch" },
|
|
301
|
-
noise: { tiny: "ign", small: "stipple", large: "stipple" },
|
|
302
|
-
pattern: { tiny: "checker", small: "bayer4", large: "weave" }
|
|
303
|
-
};
|
|
304
|
-
var TARGET_STEPS = { tiny: 3, small: 5, large: 7 };
|
|
305
|
-
var SCREEN_MIN_BAND = {
|
|
306
|
-
bayer2: "tiny",
|
|
307
|
-
checker: "tiny",
|
|
308
|
-
ign: "tiny",
|
|
309
|
-
bayer4: "small",
|
|
310
|
-
halftone: "small",
|
|
311
|
-
lines: "small",
|
|
312
|
-
diagonal: "small",
|
|
313
|
-
stipple: "small",
|
|
314
|
-
bayer8: "large",
|
|
315
|
-
chunky: "large",
|
|
316
|
-
coarseDot: "large",
|
|
317
|
-
hatch: "large",
|
|
318
|
-
weave: "large"
|
|
319
|
-
};
|
|
320
|
-
var BAND_ORDER = { tiny: 0, small: 1, large: 2 };
|
|
321
|
-
function bandOf(width, height = width) {
|
|
322
|
-
const side = Math.min(width, height);
|
|
323
|
-
for (const [band, max] of TARGET_BANDS) if (side <= max) return band;
|
|
324
|
-
return "large";
|
|
325
|
-
}
|
|
326
|
-
function screenForTarget(width, height = width, preference = "ordered") {
|
|
327
|
-
const band = bandOf(width, height);
|
|
328
|
-
let family;
|
|
329
|
-
let id;
|
|
330
|
-
if (SCREENS[preference]) {
|
|
331
|
-
const pref = preference;
|
|
332
|
-
family = SCREEN_KIND[pref][0];
|
|
333
|
-
id = BAND_ORDER[SCREEN_MIN_BAND[pref]] <= BAND_ORDER[band] ? pref : TARGET_SCREENS[family][band];
|
|
334
|
-
} else if (TARGET_SCREENS[preference]) {
|
|
335
|
-
family = preference;
|
|
336
|
-
id = TARGET_SCREENS[family][band];
|
|
337
|
-
} else {
|
|
338
|
-
throw new RangeError(`Unknown screen or family: ${preference}`);
|
|
339
|
-
}
|
|
340
|
-
return { id, steps: TARGET_STEPS[band], bias: 0, family, band };
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// ../../../../../../keel-engine/packages/core/src/quantize.ts
|
|
344
|
-
var BAYER4Q = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5].map((v) => (v + 0.5) / 16);
|
|
345
|
-
|
|
346
|
-
// ../../../../../../keel-engine/packages/core/src/gif.ts
|
|
347
|
-
var DICT = new Int32Array(1 << 17).fill(-1);
|
|
348
|
-
var DICT_TOUCHED = new Int32Array(1 << 12);
|
|
349
|
-
|
|
350
|
-
// ../../../../../../keel-engine/packages/core/src/targets.ts
|
|
351
|
-
var preset = (id, label, kind, width, height, paletteLimit, channelBits, screen, hardware = null) => Object.freeze({ id, label, kind, width, height, paletteLimit, channelBits, screen, dither: screen ? 0.9 : 0, hardware });
|
|
352
|
-
var TARGET_PROFILES = Object.freeze({
|
|
353
|
-
native: preset("native", "Original", "native", 128, 128, 65536, 8, 4),
|
|
354
|
-
"8-bit": preset("8-bit", "8-bit style", "style", 256, 240, 16, 8, 2),
|
|
355
|
-
"16-bit": preset("16-bit", "16-bit style", "style", 320, 240, 256, 5, 4),
|
|
356
|
-
"32-bit": preset("32-bit", "32-bit style", "style", 512, 384, 32768, 5, 4),
|
|
357
|
-
"64-bit": preset("64-bit", "64-bit style", "style", 640, 480, 65536, 8, 8),
|
|
358
|
-
"game-boy": preset("game-boy", "Game Boy", "hardware", 160, 144, 4, 8, 2, "game-boy"),
|
|
359
|
-
"game-boy-color": preset("game-boy-color", "Game Boy Color", "hardware", 160, 144, 32, 5, 2, "game-boy-color"),
|
|
360
|
-
chromatic: preset("chromatic", "ModRetro Chromatic", "hardware", 160, 144, 32, 5, 2, "game-boy-color")
|
|
361
|
-
});
|
|
362
|
-
function targetProfile(input) {
|
|
363
|
-
const id = typeof input === "number" ? `${input}-bit` : input;
|
|
364
|
-
if (!Object.hasOwn(TARGET_PROFILES, id)) throw new RangeError(`Unknown target: ${id}`);
|
|
365
|
-
return TARGET_PROFILES[id];
|
|
366
|
-
}
|
|
367
|
-
var byte = (n) => {
|
|
368
|
-
if (!Number.isFinite(n)) throw new RangeError("Colour channels must be finite");
|
|
369
|
-
return Math.max(0, Math.min(255, Math.round(n)));
|
|
370
|
-
};
|
|
371
|
-
var colour = (c) => [byte(c[0] ?? 0), byte(c[1] ?? 0), byte(c[2] ?? 0)];
|
|
372
|
-
var key = (c) => c[0] << 16 | c[1] << 8 | c[2];
|
|
373
|
-
var light = (c) => c[0] * 299 + c[1] * 587 + c[2] * 114;
|
|
374
|
-
function rgb555(c) {
|
|
375
|
-
const [r, g, b] = colour(c).map((v) => Math.round(v * 31 / 255));
|
|
376
|
-
return r | g << 5 | b << 10;
|
|
377
|
-
}
|
|
378
|
-
function fromRgb555(word) {
|
|
379
|
-
return [Math.round((word & 31) * 255 / 31), Math.round((word >> 5 & 31) * 255 / 31), Math.round((word >> 10 & 31) * 255 / 31)];
|
|
380
|
-
}
|
|
381
|
-
function nearestColour(c, palette) {
|
|
382
|
-
if (!palette.length) throw new RangeError("A palette must contain a colour");
|
|
383
|
-
let best = 0, distance = Infinity;
|
|
384
|
-
for (let i = 0; i < palette.length; i += 1) {
|
|
385
|
-
const p = palette[i];
|
|
386
|
-
const d = (c[0] - p[0]) ** 2 * 299 + (c[1] - p[1]) ** 2 * 587 + (c[2] - p[2]) ** 2 * 114;
|
|
387
|
-
if (d < distance) {
|
|
388
|
-
distance = d;
|
|
389
|
-
best = i;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return best;
|
|
393
|
-
}
|
|
394
|
-
function box(samples) {
|
|
395
|
-
let axis = 0, range = -1;
|
|
396
|
-
for (let a = 0; a < 3; a += 1) {
|
|
397
|
-
let lo = 255, hi = 0;
|
|
398
|
-
for (const s of samples) {
|
|
399
|
-
lo = Math.min(lo, s.c[a]);
|
|
400
|
-
hi = Math.max(hi, s.c[a]);
|
|
401
|
-
}
|
|
402
|
-
if (hi - lo > range) {
|
|
403
|
-
axis = a;
|
|
404
|
-
range = hi - lo;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
return { samples, axis, score: samples.length > 1 ? range * samples.reduce((n, s) => n + s.count, 0) : -1 };
|
|
408
|
-
}
|
|
409
|
-
function reduceColours(input, limit) {
|
|
410
|
-
if (!Number.isSafeInteger(limit) || limit < 1) throw new RangeError("Palette limit must be a positive integer");
|
|
411
|
-
const histogram = /* @__PURE__ */ new Map();
|
|
412
|
-
for (const raw of input) {
|
|
413
|
-
const c = colour(raw), k = key(c), old = histogram.get(k);
|
|
414
|
-
if (old) old.count += 1;
|
|
415
|
-
else histogram.set(k, { c, count: 1 });
|
|
416
|
-
}
|
|
417
|
-
const samples = [...histogram.values()].sort((a, b) => key(a.c) - key(b.c));
|
|
418
|
-
if (samples.length <= limit) return samples.map((s) => [...s.c]);
|
|
419
|
-
const boxes = [box(samples)];
|
|
420
|
-
while (boxes.length < limit) {
|
|
421
|
-
let selected = 0;
|
|
422
|
-
for (let i = 1; i < boxes.length; i += 1) if (boxes[i].score > boxes[selected].score) selected = i;
|
|
423
|
-
const b = boxes[selected];
|
|
424
|
-
if (b.score < 0) break;
|
|
425
|
-
b.samples.sort((a, z) => a.c[b.axis] - z.c[b.axis] || key(a.c) - key(z.c));
|
|
426
|
-
const half = b.samples.reduce((n, s) => n + s.count, 0) / 2;
|
|
427
|
-
let total = 0, split = 0;
|
|
428
|
-
do {
|
|
429
|
-
total += b.samples[split++].count;
|
|
430
|
-
} while (total < half && split < b.samples.length - 1);
|
|
431
|
-
boxes.splice(selected, 1, box(b.samples.slice(0, split)), box(b.samples.slice(split)));
|
|
432
|
-
}
|
|
433
|
-
return boxes.map((b) => {
|
|
434
|
-
const weight = b.samples.reduce((n, s) => n + s.count, 0);
|
|
435
|
-
const mean = [0, 0, 0];
|
|
436
|
-
for (const s of b.samples) for (let a = 0; a < 3; a += 1) mean[a] = mean[a] + s.c[a] * s.count / weight;
|
|
437
|
-
return [...b.samples[nearestColour(mean, b.samples.map((s) => s.c))].c];
|
|
438
|
-
}).sort((a, b) => light(a) - light(b) || key(a) - key(b));
|
|
439
|
-
}
|
|
440
|
-
function adaptTargetPalette(input, target) {
|
|
441
|
-
const profile2 = targetProfile(target);
|
|
442
|
-
const colours = input.map((c) => {
|
|
443
|
-
const rgb = colour(c);
|
|
444
|
-
if (profile2.hardware === "game-boy") {
|
|
445
|
-
const v = Math.round(light(rgb) / 85e3) * 85;
|
|
446
|
-
return [v, v, v];
|
|
447
|
-
}
|
|
448
|
-
return profile2.channelBits === 5 ? fromRgb555(rgb555(rgb)) : rgb;
|
|
449
|
-
});
|
|
450
|
-
const palette = reduceColours(colours, profile2.paletteLimit);
|
|
451
|
-
const cache = new Map(palette.map((c) => [key(c), c]));
|
|
452
|
-
return colours.map((c) => {
|
|
453
|
-
const k = key(c);
|
|
454
|
-
let found = cache.get(k);
|
|
455
|
-
if (!found) {
|
|
456
|
-
found = palette[nearestColour(c, palette)];
|
|
457
|
-
cache.set(k, found);
|
|
458
|
-
}
|
|
459
|
-
return [...found];
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// ../../../../../../keel-engine/packages/render/src/fx.ts
|
|
464
|
-
var FX_ORDER = ["crt", "grade", "fog", "glow", "rim", "flash", "vignette", "scanlines", "dither", "outline", "cycle"];
|
|
465
|
-
var scalePx = (v, side, min = 1, max = 64) => Math.max(min, Math.min(max, Math.round(v * side / 128)));
|
|
466
|
-
var GRADE_PRESETS = { day: { shift: 0 }, dusk: { shift: -0.6 }, night: { shift: -1.4 } };
|
|
467
|
-
var FX = {
|
|
468
|
-
// A tube's bend: whole pixels moved outward from the middle, the corners gone to a border entry. (From 64 px.)
|
|
469
|
-
crt: { defaults: { curve: 0.08, border: { ramp: 0, index: 0 }, minSize: 64 }, resolve: (p, side) => side < p.minSize ? { off: `below ${p.minSize} px` } : { curve: p.curve, border: p.border } },
|
|
470
|
-
// Colour grading by ramps: each ramp swapped for its graded twin (map: { stone: "stoneNight" }), then shifted along it.
|
|
471
|
-
grade: {
|
|
472
|
-
defaults: { preset: "day", shift: null, map: {} },
|
|
473
|
-
resolve: (p) => ({ shift: p.shift ?? (Object.hasOwn(GRADE_PRESETS, p.preset) ? GRADE_PRESETS[p.preset] : GRADE_PRESETS.day).shift, map: { ...p.map } })
|
|
474
|
-
},
|
|
475
|
-
// Fog by distance: past `near`, pixels go over to the fog ramp -- dithered, by the screen -- all of them by `far`.
|
|
476
|
-
fog: { defaults: { ramp: "sky", near: 25, far: 90, amount: 1, light: 0.3 }, resolve: (p) => ({ ...p }) },
|
|
477
|
-
// Glow: emissive materials (glow >= threshold) climb `self` entries; round them a halo `radius` px (at 128) climbs `halo`.
|
|
478
|
-
glow: {
|
|
479
|
-
defaults: { radius: 3, halo: 2.5, self: 1, threshold: 0.2, tint: false },
|
|
480
|
-
resolve: (p, side) => ({ ...p, radius: scalePx(p.radius, side, 1, 16) })
|
|
481
|
-
},
|
|
482
|
-
// Rim light: an edge on the light's side (`dir`: "sun" or a screen direction [x, y]) climbs `steps`; `width` px at 128.
|
|
483
|
-
rim: { defaults: { width: 1, steps: 1.5, dir: "sun" }, resolve: (p, side) => ({ ...p, width: scalePx(p.width, side, 1, 4) }) },
|
|
484
|
-
// Hit flash: the struck thing -- by material (`mats`) or id range (`ids: [from, to]`) -- goes up by `amount` (0..1), or onto `ramp`.
|
|
485
|
-
flash: { defaults: { amount: 0, mats: [], ids: null, ramp: null }, resolve: (p) => ({ ...p, amount: Math.max(0, Math.min(1, p.amount)), mats: p.mats.slice(0, 8) }) },
|
|
486
|
-
// Vignette: from `inner` to `outer` (fractions of the half-diagonal), `steps` down the ramps, dithered.
|
|
487
|
-
vignette: { defaults: { inner: 0.55, outer: 1.1, steps: 2 }, resolve: (p) => ({ ...p }) },
|
|
488
|
-
// Scanlines: `dark` rows of every `period` (px at 128) a step down. Only from `minSize` px: below it they are the picture.
|
|
489
|
-
scanlines: {
|
|
490
|
-
defaults: { period: 2, steps: 1, minSize: 96 },
|
|
491
|
-
resolve: (p, side) => {
|
|
492
|
-
if (side < p.minSize) return { off: `below ${p.minSize} px` };
|
|
493
|
-
const period = Math.max(2, scalePx(p.period, side, 2, 16));
|
|
494
|
-
return { period, dark: Math.max(1, Math.floor(period / 2)), steps: p.steps };
|
|
495
|
-
}
|
|
496
|
-
},
|
|
497
|
-
// The dither screen: a core screen id or family ("ordered", "dot", "line", "noise", "pattern"), or "auto"; `amount` 0..1.
|
|
498
|
-
dither: {
|
|
499
|
-
defaults: { screen: "auto", amount: 0.9 },
|
|
500
|
-
resolve: (p, _side, W2, H) => {
|
|
501
|
-
if (p.screen === "none" || p.amount <= 0) return { screen: "none", amount: 0 };
|
|
502
|
-
const pref = p.screen === "auto" ? "ordered" : p.screen;
|
|
503
|
-
return { screen: screenForTarget(W2, H, pref).id, amount: p.amount };
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
// The outline: "all" (a thing against anything behind it -- the classic), "outer" (only across `gap` metres:
|
|
507
|
-
// silhouettes, not the parts inside them), "none"; `steps` darker, or in one `color` { ramp, index }.
|
|
508
|
-
outline: {
|
|
509
|
-
defaults: { mode: "all", steps: 3, gap: 1.5, color: null },
|
|
510
|
-
resolve: (p) => p.mode === "none" ? { off: "mode none" } : { mode: p.mode, steps: p.steps, gap: p.mode === "outer" ? p.gap : 0.56, color: p.color }
|
|
511
|
-
},
|
|
512
|
-
// Palette cycling: named ramps' upper entries (from `from`, a fraction of the ramp) turn over at `speed` entries a second.
|
|
513
|
-
cycle: {
|
|
514
|
-
defaults: { ramps: {}, speed: 3, from: 0.5 },
|
|
515
|
-
resolve: (p) => {
|
|
516
|
-
const list = isList(p.ramps) ? Object.fromEntries(p.ramps.map((r) => [r, {}])) : p.ramps;
|
|
517
|
-
return { ramps: Object.fromEntries(Object.entries(list).map(([r, o]) => [r, { speed: o.speed ?? p.speed, from: o.from ?? p.from }])) };
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
var isList = (v) => Array.isArray(v);
|
|
522
|
-
var FX_NAMES = Object.keys(FX);
|
|
523
|
-
var known = (name) => typeof name === "string" && Object.hasOwn(FX, name);
|
|
524
|
-
var dims = (target) => {
|
|
525
|
-
if (typeof target === "number") return [target, target];
|
|
526
|
-
if ("width" in target) return [target.width, target.height];
|
|
527
|
-
return [target[0], target[1]];
|
|
528
|
-
};
|
|
529
|
-
function resolveFx(list = [], target = 128) {
|
|
530
|
-
const [W2, H] = dims(target);
|
|
531
|
-
const side = Math.min(W2, H);
|
|
532
|
-
const byName = /* @__PURE__ */ new Map();
|
|
533
|
-
for (const e of list) {
|
|
534
|
-
if (!e || !known(e.name)) throw new RangeError(`Unknown fx pass: ${String(e?.name)}`);
|
|
535
|
-
byName.set(e.name, { ...byName.get(e.name) ?? {}, ...e });
|
|
536
|
-
}
|
|
537
|
-
const out = [];
|
|
538
|
-
for (const name of FX_ORDER) {
|
|
539
|
-
const entry = byName.get(name);
|
|
540
|
-
if (!entry) continue;
|
|
541
|
-
const { name: _, on = true, ...given } = entry;
|
|
542
|
-
out.push(resolveOne(name, Boolean(on), given, side, W2, H));
|
|
543
|
-
}
|
|
544
|
-
return out;
|
|
545
|
-
}
|
|
546
|
-
function resolveOne(name, on, given, side, W2, H) {
|
|
547
|
-
const def = FX[name];
|
|
548
|
-
const params = { ...def.defaults, ...given };
|
|
549
|
-
if (!on) return { name, on: false, params, note: "turned off" };
|
|
550
|
-
const r = def.resolve(params, side, W2, H);
|
|
551
|
-
if ("off" in r && r.off) return { name, on: false, params, note: r.off };
|
|
552
|
-
return { name, on: true, params: r };
|
|
553
|
-
}
|
|
554
|
-
function toggleFx(list, name, on) {
|
|
555
|
-
if (!known(name)) throw new RangeError(`Unknown fx pass: ${String(name)}`);
|
|
556
|
-
const has = list.some((e) => e.name === name);
|
|
557
|
-
return has ? list.map((e) => e.name === name ? { ...e, on } : e) : [...list, { name, on }];
|
|
558
|
-
}
|
|
559
|
-
var ALL_FX = () => [
|
|
560
|
-
{ name: "grade", preset: "dusk" },
|
|
561
|
-
{ name: "fog" },
|
|
562
|
-
{ name: "glow" },
|
|
563
|
-
{ name: "rim" },
|
|
564
|
-
{ name: "vignette" },
|
|
565
|
-
{ name: "scanlines" },
|
|
566
|
-
{ name: "dither", screen: "auto" },
|
|
567
|
-
{ name: "outline", mode: "all" },
|
|
568
|
-
{ name: "cycle", ramps: ["water"] }
|
|
569
|
-
];
|
|
570
|
-
function fxUniforms(resolved, look) {
|
|
571
|
-
const on = {};
|
|
572
|
-
for (const p of resolved) if (p.on) on[p.name] = p.params;
|
|
573
|
-
const ramp2 = (r) => typeof r === "number" ? r : look.ramp(r);
|
|
574
|
-
const entry = (c) => {
|
|
575
|
-
if (c == null) return -1;
|
|
576
|
-
if (typeof c === "number") return c;
|
|
577
|
-
const r = ramp2(c.ramp);
|
|
578
|
-
if (r < 0) return -1;
|
|
579
|
-
const [base, len2] = look.rampOf(r);
|
|
580
|
-
return base + Math.max(0, Math.min(len2 - 1, c.index < 0 ? len2 + c.index : c.index));
|
|
581
|
-
};
|
|
582
|
-
const style = look.style ?? { screen: 4, dither: 0.9, outline: 1 };
|
|
583
|
-
let screen = style.screen;
|
|
584
|
-
let dither = style.dither;
|
|
585
|
-
if (on.dither) {
|
|
586
|
-
screen = on.dither.screen;
|
|
587
|
-
dither = on.dither.amount;
|
|
588
|
-
} else if (resolved.some((p) => p.name === "dither")) {
|
|
589
|
-
screen = 0;
|
|
590
|
-
dither = 0;
|
|
591
|
-
}
|
|
592
|
-
const bayer = { bayer2: 2, bayer4: 4, bayer8: 8, none: 0 };
|
|
593
|
-
let uScreen;
|
|
594
|
-
let tile = null;
|
|
595
|
-
if (typeof screen === "string") {
|
|
596
|
-
const b = Object.hasOwn(bayer, screen) ? bayer[screen] : void 0;
|
|
597
|
-
if (b !== void 0) uScreen = b;
|
|
598
|
-
else if (Object.hasOwn(SCREENS, screen)) {
|
|
599
|
-
uScreen = -1;
|
|
600
|
-
tile = screen;
|
|
601
|
-
} else throw new RangeError(`Unknown screen: ${screen}`);
|
|
602
|
-
} else uScreen = screen | 0;
|
|
603
|
-
const ol = resolved.find((p) => p.name === "outline");
|
|
604
|
-
let uOutline;
|
|
605
|
-
let uOutlineInk;
|
|
606
|
-
if (ol) {
|
|
607
|
-
uOutline = ol.on ? [1, ol.params.steps, ol.params.gap / look.far] : [0, 3, 4e-3];
|
|
608
|
-
uOutlineInk = ol.on ? entry(ol.params.color) : -1;
|
|
609
|
-
} else {
|
|
610
|
-
uOutline = [style.outline ? 1 : 0, 3, 4e-3];
|
|
611
|
-
uOutlineInk = -1;
|
|
612
|
-
}
|
|
613
|
-
const { crt, fog, glow, rim, flash: fl, vignette: vig, scanlines: sc, cycle: cy } = on;
|
|
614
|
-
const mats = fl ? [...fl.mats, -1, -1, -1, -1, -1, -1, -1, -1].slice(0, 8) : Array(8).fill(-1);
|
|
615
|
-
const cycle = [];
|
|
616
|
-
if (cy) {
|
|
617
|
-
for (const [name, o] of Object.entries(cy.ramps)) {
|
|
618
|
-
const r = ramp2(name);
|
|
619
|
-
if (r < 0) continue;
|
|
620
|
-
const len2 = look.rampOf(r)[1];
|
|
621
|
-
cycle.push([r, Math.min(len2 - 1, Math.max(0, Math.round(o.from * len2))), o.speed]);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
const grade = [];
|
|
625
|
-
if (on.grade) for (const [from, to] of Object.entries(on.grade.map)) {
|
|
626
|
-
const a = ramp2(from);
|
|
627
|
-
const b = ramp2(to);
|
|
628
|
-
if (a >= 0 && b >= 0) grade.push([a, b]);
|
|
629
|
-
}
|
|
630
|
-
const u = {
|
|
631
|
-
uScreen,
|
|
632
|
-
uDither: dither,
|
|
633
|
-
uOutline,
|
|
634
|
-
uOutlineInk,
|
|
635
|
-
uCrt: crt ? [1, crt.curve, Math.max(0, entry(crt.border)), 0] : [0, 0, 0, 0],
|
|
636
|
-
uGrade: on.grade ? [1, on.grade.shift] : [0, 0],
|
|
637
|
-
uFog: fog ? [ramp2(fog.ramp) >= 0 ? 1 : 0, fog.near, fog.far, fog.amount] : [0, 0, 1, 0],
|
|
638
|
-
uFogLook: fog ? [Math.max(0, ramp2(fog.ramp)), fog.light] : [0, 0],
|
|
639
|
-
uGlow: glow ? [1, glow.radius, glow.halo, glow.self] : [0, 1, 0, 0],
|
|
640
|
-
uGlowK: glow ? [glow.threshold, glow.tint ? 1 : 0] : [1, 0],
|
|
641
|
-
uRim: rim ? [1, rim.width, rim.steps, 0] : [0, 1, 0, 0],
|
|
642
|
-
uRimDir: rim && isPair(rim.dir) ? rim.dir : null,
|
|
643
|
-
// (null: the renderer aims it at the sun)
|
|
644
|
-
uFlash: fl ? [fl.amount, fl.ramp == null ? -1 : ramp2(fl.ramp), fl.ids ? fl.ids[0] : 1, fl.ids ? fl.ids[1] : 0] : [0, -1, 1, 0],
|
|
645
|
-
uFlashMats: mats,
|
|
646
|
-
uVig: vig ? [1, vig.inner, vig.outer, vig.steps] : [0, 0, 1, 0],
|
|
647
|
-
uScan: sc ? [1, sc.period, sc.dark, sc.steps] : [0, 2, 0, 0],
|
|
648
|
-
uCycle: cycle.length ? 1 : 0
|
|
649
|
-
};
|
|
650
|
-
return { u, ramps: { cycle, grade }, screen: tile };
|
|
651
|
-
}
|
|
652
|
-
var isPair = (v) => Array.isArray(v);
|
|
653
|
-
function screenTile(id, tile = 192) {
|
|
654
|
-
const at = SCREENS[id].at;
|
|
655
|
-
const out = new Uint8Array(tile * tile);
|
|
656
|
-
for (let y = 0; y < tile; y += 1) for (let x = 0; x < tile; x += 1) out[y * tile + x] = Math.max(0, Math.min(255, Math.round(at(x, y) * 255)));
|
|
657
|
-
return out;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// ../../../../../../keel-engine/packages/render/src/shaders.ts
|
|
661
|
-
var MAX_BOXES = 256;
|
|
662
|
-
var MAX_WEDGES = 128;
|
|
663
|
-
var MAX_CAPS = 256;
|
|
664
|
-
var MAX_RAMPS = 256;
|
|
665
|
-
var MAX_MATERIALS = 255;
|
|
666
|
-
var PALETTE_WIDTH = 1024;
|
|
667
|
-
var MAX_COLOURS = PALETTE_WIDTH * 64;
|
|
668
|
-
var SCREEN_TILE = 192;
|
|
669
|
-
var FAR = 140;
|
|
670
|
-
var WATER_MAT = 4;
|
|
671
|
-
var SKY_MAT = 5;
|
|
672
|
-
var PARTICLE_MAT = 255;
|
|
673
|
-
var FULLSCREEN_VS = `#version 300 es
|
|
674
|
-
in vec2 aPos;
|
|
675
|
-
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }`;
|
|
676
|
-
var WORLD_FS = `#version 300 es
|
|
677
|
-
precision highp float;
|
|
678
|
-
precision highp sampler2D;
|
|
679
|
-
#define MAXB ${MAX_BOXES}
|
|
680
|
-
#define MAXW ${MAX_WEDGES}
|
|
681
|
-
#define MAXC ${MAX_CAPS}
|
|
682
|
-
#define FAR ${FAR.toFixed(1)}
|
|
683
|
-
uniform vec2 uRes;
|
|
684
|
-
uniform vec3 uEye, uFwd, uRight, uUp;
|
|
685
|
-
uniform float uTan, uTime;
|
|
686
|
-
uniform int uBoxes, uWedges, uCaps;
|
|
687
|
-
// The solids, in uniform blocks (fast to read in the march's inner loop; each block well under the 16 KB baseline).
|
|
688
|
-
layout(std140) uniform Boxes { vec4 uBox[2 * MAXB]; }; // [centre, mat][half, yaw]
|
|
689
|
-
layout(std140) uniform Wedges { vec4 uWedge[3 * MAXW]; }; // [centre, mat][half, yaw][lo, 0, 0, 0]
|
|
690
|
-
layout(std140) uniform Capsules { vec4 uCap[2 * MAXC]; }; // [a, r][b, mat]
|
|
691
|
-
uniform sampler2D uMats; // row 0 per material: ramp, lightness scale, pattern (0 none, 1 checker), emissive
|
|
692
|
-
uniform vec3 uSun;
|
|
693
|
-
uniform float uWaterY, uFogNear, uFogFar;
|
|
694
|
-
layout(location = 0) out vec4 outData; // lightness, ramp / 255, material / 255, id / 255
|
|
695
|
-
layout(location = 1) out vec4 outData2; // glow, facing, 0, 0
|
|
696
|
-
|
|
697
|
-
float sdBox(vec3 p, vec3 b) { vec3 q = abs(p) - b; return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0); }
|
|
698
|
-
float sdCap(vec3 p, vec3 a, vec3 b, float r) { vec3 pa = p - a, ba = b - a; float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-6), 0.0, 1.0); return length(pa - ba * h) - r; }
|
|
699
|
-
// A wedge's cross-section in (z, y): the foot at +z (lo x its height), rising to full height at -z.
|
|
700
|
-
// Exact for a convex polygon: outside, the nearest edge; inside, the nearest edge's plane.
|
|
701
|
-
// (physics/character.js wedgeDistance is the same solid.)
|
|
702
|
-
float sdSection(vec2 p, vec2 h, float lo) {
|
|
703
|
-
vec2 v[4] = vec2[4](vec2(-h.x, -h.y), vec2(h.x, -h.y), vec2(h.x, -h.y + 2.0 * h.y * lo), vec2(-h.x, h.y));
|
|
704
|
-
float out2 = 1e18;
|
|
705
|
-
float far = -1e9;
|
|
706
|
-
bool inside = true;
|
|
707
|
-
for (int i = 0; i < 4; i++) {
|
|
708
|
-
vec2 a = v[i];
|
|
709
|
-
vec2 e = v[(i + 1) & 3] - a;
|
|
710
|
-
float L2 = dot(e, e);
|
|
711
|
-
if (L2 < 1e-12) continue;
|
|
712
|
-
vec2 w = p - a;
|
|
713
|
-
vec2 q = w - e * clamp(dot(w, e) / L2, 0.0, 1.0);
|
|
714
|
-
out2 = min(out2, dot(q, q));
|
|
715
|
-
float s = (w.x * e.y - w.y * e.x) * inversesqrt(L2);
|
|
716
|
-
if (s > 0.0) inside = false;
|
|
717
|
-
far = max(far, s);
|
|
718
|
-
}
|
|
719
|
-
return inside ? far : sqrt(out2);
|
|
720
|
-
}
|
|
721
|
-
float sdWedge(vec3 q, vec3 h, float lo) {
|
|
722
|
-
float a = abs(q.x) - h.x;
|
|
723
|
-
float b = sdSection(q.zy, h.zy, lo);
|
|
724
|
-
return length(max(vec2(a, b), 0.0)) + min(max(a, b), 0.0);
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// The world: nearest distance and which thing (material in .y, index in .z).
|
|
728
|
-
vec3 map(vec3 p) {
|
|
729
|
-
vec3 best = vec3(1e9, -1.0, -1.0);
|
|
730
|
-
for (int i = 0; i < MAXB; i++) {
|
|
731
|
-
if (i >= uBoxes) break;
|
|
732
|
-
vec4 A = uBox[2 * i]; vec4 B = uBox[2 * i + 1];
|
|
733
|
-
vec3 q = p - A.xyz;
|
|
734
|
-
float c = cos(B.w), s = sin(B.w);
|
|
735
|
-
q.xz = mat2(c, s, -s, c) * q.xz; // (world -> the box's frame, core/frame.js: x' = c x - s z, z' = s x + c z)
|
|
736
|
-
float d = sdBox(q, B.xyz);
|
|
737
|
-
if (d < best.x) best = vec3(d, A.w, float(i));
|
|
738
|
-
}
|
|
739
|
-
for (int i = 0; i < MAXW; i++) {
|
|
740
|
-
if (i >= uWedges) break;
|
|
741
|
-
vec4 A = uWedge[3 * i]; vec4 B = uWedge[3 * i + 1];
|
|
742
|
-
float lo = uWedge[3 * i + 2].x;
|
|
743
|
-
vec3 q = p - A.xyz;
|
|
744
|
-
float c = cos(B.w), s = sin(B.w);
|
|
745
|
-
q.xz = mat2(c, s, -s, c) * q.xz; // (the same turn as a box)
|
|
746
|
-
float d = sdWedge(q, B.xyz, lo);
|
|
747
|
-
// (A tapered wedge: its right and left edges run from its span at the back, -z, in to the foot's full width.)
|
|
748
|
-
vec2 span = uWedge[3 * i + 2].zw * B.x;
|
|
749
|
-
float mr = (B.x - span.y) / max(0.0001, 2.0 * B.z), ml = (B.x + span.x) / max(0.0001, 2.0 * B.z);
|
|
750
|
-
d = max(d, (q.x - mr * q.z - (B.x + span.y) * 0.5) / sqrt(1.0 + mr * mr));
|
|
751
|
-
d = max(d, (-q.x - ml * q.z - (B.x - span.x) * 0.5) / sqrt(1.0 + ml * ml));
|
|
752
|
-
float skin = uWedge[3 * i + 2].y;
|
|
753
|
-
if (skin > 0.0) {
|
|
754
|
-
float slope = -B.y * (1.0 - lo) / max(0.0001, B.z);
|
|
755
|
-
float top = B.y + slope * (q.z + B.z);
|
|
756
|
-
d = max(d, (top - skin - q.y) / sqrt(1.0 + slope * slope));
|
|
757
|
-
}
|
|
758
|
-
if (d < best.x) best = vec3(d, A.w, float(200 + i));
|
|
759
|
-
}
|
|
760
|
-
for (int i = 0; i < MAXC; i++) {
|
|
761
|
-
if (i >= uCaps) break;
|
|
762
|
-
vec4 A = uCap[2 * i]; vec4 B = uCap[2 * i + 1];
|
|
763
|
-
float d = sdCap(p, A.xyz, B.xyz, A.w);
|
|
764
|
-
if (d < best.x) best = vec3(d, B.w, float(100 + i));
|
|
765
|
-
}
|
|
766
|
-
return best;
|
|
767
|
-
}
|
|
768
|
-
vec3 normalAt(vec3 p) {
|
|
769
|
-
const vec2 k = vec2(1.0, -1.0);
|
|
770
|
-
const float e = 0.002;
|
|
771
|
-
return normalize(k.xyy * map(p + k.xyy * e).x + k.yyx * map(p + k.yyx * e).x + k.yxy * map(p + k.yxy * e).x + k.xxx * map(p + k.xxx * e).x);
|
|
772
|
-
}
|
|
773
|
-
float shadowAt(vec3 p, vec3 l) {
|
|
774
|
-
float res = 1.0, t = 0.05;
|
|
775
|
-
for (int i = 0; i < 24; i++) {
|
|
776
|
-
float h = map(p + l * t).x;
|
|
777
|
-
res = min(res, 10.0 * h / t);
|
|
778
|
-
t += clamp(h, 0.04, 0.6);
|
|
779
|
-
if (res < 0.02 || t > 18.0) break;
|
|
780
|
-
}
|
|
781
|
-
return clamp(res, 0.0, 1.0);
|
|
782
|
-
}
|
|
783
|
-
float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
|
|
784
|
-
float vnoise(vec2 p) { vec2 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f); return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); }
|
|
785
|
-
float fbm(vec2 p) { float v = 0.0, a = 0.5; for (int i = 0; i < 5; i++) { v += a * vnoise(p); p *= 2.03; a *= 0.5; } return v; }
|
|
786
|
-
|
|
787
|
-
// Checker tiles on whichever face a point is on, with a dark joint.
|
|
788
|
-
float tiles(vec3 p, vec3 n) {
|
|
789
|
-
vec2 uv = abs(n.y) > 0.6 ? p.xz : abs(n.x) > abs(n.z) ? p.zy : p.xy;
|
|
790
|
-
vec2 g = uv * 1.0;
|
|
791
|
-
vec2 f = fract(g);
|
|
792
|
-
float joint = step(0.95, max(f.x, f.y)) * 0.06;
|
|
793
|
-
float chk = mod(floor(g.x) + floor(g.y), 2.0);
|
|
794
|
-
return 0.16 * chk - 0.08 - joint;
|
|
795
|
-
}
|
|
796
|
-
vec4 matOf(int i) { return texelFetch(uMats, ivec2(i, 0), 0); }
|
|
797
|
-
|
|
798
|
-
void main() {
|
|
799
|
-
vec2 uv = (gl_FragCoord.xy / uRes) * 2.0 - 1.0;
|
|
800
|
-
float aspect = uRes.x / uRes.y;
|
|
801
|
-
vec3 rd = normalize(uFwd + uv.x * uTan * aspect * uRight + uv.y * uTan * uUp);
|
|
802
|
-
vec3 ro = uEye;
|
|
803
|
-
float t = 0.05;
|
|
804
|
-
vec3 hit = vec3(1e9, -1.0, -1.0);
|
|
805
|
-
for (int i = 0; i < 110; i++) {
|
|
806
|
-
vec3 h = map(ro + rd * t);
|
|
807
|
-
if (h.x < 0.0015 * t) { hit = vec3(t, h.y, h.z); break; }
|
|
808
|
-
t += h.x;
|
|
809
|
-
if (t > FAR) break;
|
|
810
|
-
}
|
|
811
|
-
float tw = rd.y < -1e-4 ? (uWaterY - ro.y) / rd.y : 1e9;
|
|
812
|
-
float L; float ramp; float id; float depth; float mat; float glow = 0.0; float facing = 1.0;
|
|
813
|
-
if (tw < hit.x && tw < FAR) {
|
|
814
|
-
// The water: dark and deep, bright where it meets what stands in it, rippled, catching the sky.
|
|
815
|
-
vec3 p = ro + rd * tw;
|
|
816
|
-
float edge = clamp(1.0 - map(p).x / 0.9, 0.0, 1.0);
|
|
817
|
-
float ripple = fbm(p.xz * 1.7 + vec2(uTime * 0.35, uTime * 0.2)) ;
|
|
818
|
-
float glint = step(0.72, fbm(p.xz * 5.0 + uTime * 0.8)) * 0.35 * clamp(1.0 - tw / 40.0, 0.0, 1.0);
|
|
819
|
-
L = 0.1 + 0.16 * ripple * ripple + 0.7 * pow(edge, 3.0) + glint;
|
|
820
|
-
L *= mix(1.0, 0.45, clamp((tw - uFogNear) / (uFogFar - uFogNear), 0.0, 1.0));
|
|
821
|
-
ramp = matOf(${WATER_MAT}).x; id = 254.0; depth = tw / FAR; mat = ${WATER_MAT}.0; facing = -rd.y;
|
|
822
|
-
} else if (hit.y >= 0.0) {
|
|
823
|
-
vec3 p = ro + rd * hit.x;
|
|
824
|
-
vec3 n = normalAt(p);
|
|
825
|
-
int mi = int(hit.y + 0.5);
|
|
826
|
-
vec4 mr = matOf(mi);
|
|
827
|
-
float sh = shadowAt(p + n * 0.01, uSun);
|
|
828
|
-
float diff = max(dot(n, uSun), 0.0) * sh;
|
|
829
|
-
// (Water light: the glow off the water reaches the bottom of walls.)
|
|
830
|
-
float wglow = clamp(1.0 - (p.y - uWaterY) / 1.6, 0.0, 1.0) * max(0.0, -n.y * 0.2 + 0.8) * 0.35;
|
|
831
|
-
L = (0.16 + 0.1 * n.y + 0.5 * diff) * mr.y + wglow + mr.w; // (headroom: the brightest lit face stays under the top of its ramp)
|
|
832
|
-
// (Patterns know the resolution: a tile a few pixels across fades rather than aliasing, and small targets
|
|
833
|
-
// quieten every pattern so the subject still reads.)
|
|
834
|
-
float tilePx = uRes.y / max(hit.x * 2.0 * uTan, 1e-3);
|
|
835
|
-
float patternK = smoothstep(2.5, 7.0, tilePx) * (0.35 + 0.65 * smoothstep(28.0, 96.0, uRes.y));
|
|
836
|
-
if (mr.z > 0.5 && mr.z < 1.5) L += tiles(p, n) * patternK;
|
|
837
|
-
L = mix(L, 0.12, clamp((hit.x - uFogNear) / (uFogFar - uFogNear), 0.0, 1.0));
|
|
838
|
-
ramp = mr.x; id = mod(hit.z, 250.0); depth = hit.x / FAR; mat = float(mi);
|
|
839
|
-
glow = clamp(mr.w, 0.0, 1.0); facing = clamp(dot(n, -rd), 0.0, 1.0);
|
|
840
|
-
} else {
|
|
841
|
-
// The sky: a lid of low cloud, lit warm from somewhere, dark at the horizon.
|
|
842
|
-
float up = max(rd.y, 0.0);
|
|
843
|
-
vec2 sp = rd.xz / (rd.y + 0.12) * 1.4 + vec2(uTime * 0.02, 0.0);
|
|
844
|
-
float cl = fbm(sp) * smoothstep(0.02, 0.35, up);
|
|
845
|
-
L = 0.08 + cl * 0.75;
|
|
846
|
-
ramp = matOf(${SKY_MAT}).x; id = 255.0; depth = 1.0; mat = ${SKY_MAT}.0;
|
|
847
|
-
}
|
|
848
|
-
outData = vec4(clamp(L, 0.0, 1.0), ramp / 255.0, mat / 255.0, id / 255.0);
|
|
849
|
-
outData2 = vec4(glow, facing, 0.0, 0.0);
|
|
850
|
-
gl_FragDepth = clamp(depth, 0.0, 1.0);
|
|
851
|
-
}`;
|
|
852
|
-
var POINTS_VS = `#version 300 es
|
|
853
|
-
in vec4 aPos; // xyz, size
|
|
854
|
-
in vec3 aLook; // lightness, ramp, glow
|
|
855
|
-
uniform vec3 uEye, uFwd, uRight, uUp;
|
|
856
|
-
uniform float uTan, uAspect, uH;
|
|
857
|
-
out vec3 vLook;
|
|
858
|
-
void main() {
|
|
859
|
-
vec3 v = aPos.xyz - uEye;
|
|
860
|
-
float z = dot(v, uFwd);
|
|
861
|
-
float depth = length(v) / ${FAR.toFixed(1)};
|
|
862
|
-
gl_Position = z > 0.05 ? vec4(dot(v, uRight) / (z * uTan * uAspect), dot(v, uUp) / (z * uTan), depth * 2.0 - 1.0, 1.0) : vec4(2.0, 2.0, 2.0, 1.0);
|
|
863
|
-
gl_PointSize = max(1.0, aPos.w * 6.0 * (uH / 128.0) / max(z, 0.5)); // (a speck is the same size in the world at any target: fewer pixels, smaller dots)
|
|
864
|
-
vLook = aLook;
|
|
865
|
-
}`;
|
|
866
|
-
var POINTS_FS = `#version 300 es
|
|
867
|
-
precision highp float;
|
|
868
|
-
in vec3 vLook;
|
|
869
|
-
layout(location = 0) out vec4 outData;
|
|
870
|
-
layout(location = 1) out vec4 outData2;
|
|
871
|
-
void main() { outData = vec4(vLook.x, vLook.y / 255.0, ${PARTICLE_MAT}.0 / 255.0, 253.0 / 255.0); outData2 = vec4(vLook.z, 1.0, 0.0, 0.0); }`;
|
|
872
|
-
var PIXEL_FS = `#version 300 es
|
|
873
|
-
precision highp float;
|
|
874
|
-
precision highp sampler2D;
|
|
875
|
-
uniform sampler2D uData;
|
|
876
|
-
uniform sampler2D uData2;
|
|
877
|
-
uniform sampler2D uDepth;
|
|
878
|
-
uniform sampler2D uPalette; // colours, ${PALETTE_WIDTH} to a row
|
|
879
|
-
uniform sampler2D uRamps; // row 0 per ramp: base, length, cycle from (entry), cycle speed (entries/s); row 1: graded ramp (-1 itself)
|
|
880
|
-
uniform sampler2D uScreenTex; // a core screen's thresholds, ${SCREEN_TILE} px square
|
|
881
|
-
uniform int uScreen; // 0 none, 2 / 4 / 8: Bayer size, -1: the screen texture
|
|
882
|
-
uniform float uDither; // how far the screen reaches between two entries (0..1)
|
|
883
|
-
uniform float uTime;
|
|
884
|
-
// Outline: on, steps darker, the depth gap behind that makes an edge (metres / FAR: 0.56 m the classic, more for outer-only)
|
|
885
|
-
uniform vec3 uOutline;
|
|
886
|
-
uniform int uOutlineInk; // palette index to ink with (< 0: darken the pixel's own ramp)
|
|
887
|
-
uniform vec4 uFog; // on, near, far, amount
|
|
888
|
-
uniform vec2 uFogLook; // ramp, lightness it tends to
|
|
889
|
-
uniform vec4 uGlow; // on, halo radius (px), halo steps, self steps
|
|
890
|
-
uniform vec2 uGlowK; // threshold, tint (1: the halo wears the glowing thing's ramp)
|
|
891
|
-
uniform vec4 uVig; // on, inner, outer, steps
|
|
892
|
-
uniform vec4 uScan; // on, period (px), dark rows per period, steps
|
|
893
|
-
uniform vec4 uCrt; // on, curvature, border palette index, 0
|
|
894
|
-
uniform vec4 uRim; // on, width (px), steps, 0
|
|
895
|
-
uniform vec2 uRimDir; // screen direction the light comes from
|
|
896
|
-
uniform vec4 uFlash; // amount, ramp (-1: its own top), id from, id to
|
|
897
|
-
uniform ivec4 uFlashMats[2];// up to 8 materials (-1 none)
|
|
898
|
-
uniform vec2 uGrade; // on, shift (entries)
|
|
899
|
-
uniform int uCycle; // on
|
|
900
|
-
out vec4 outColor;
|
|
901
|
-
|
|
902
|
-
float bayer(ivec2 p, int n) {
|
|
903
|
-
if (n == 2) { int m[4] = int[4](0, 2, 3, 1); return (float(m[(p.y & 1) * 2 + (p.x & 1)]) + 0.5) / 4.0; }
|
|
904
|
-
if (n == 4) { int m[16] = int[16](0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5); return (float(m[(p.y & 3) * 4 + (p.x & 3)]) + 0.5) / 16.0; }
|
|
905
|
-
// 8x8 from 4x4
|
|
906
|
-
ivec2 q = p & 7;
|
|
907
|
-
int m4[16] = int[16](0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5);
|
|
908
|
-
int a = m4[(q.y & 3) * 4 + (q.x & 3)];
|
|
909
|
-
int b = m4[(q.y >> 2) * 4 + (q.x >> 2)];
|
|
910
|
-
return (float(a * 4 + (b & 3)) + 0.5) / 64.0;
|
|
911
|
-
}
|
|
912
|
-
float screenAt(ivec2 p) {
|
|
913
|
-
if (uScreen > 0) return bayer(p, uScreen);
|
|
914
|
-
if (uScreen < 0) return texelFetch(uScreenTex, p % ${SCREEN_TILE}, 0).r;
|
|
915
|
-
return 0.5;
|
|
916
|
-
}
|
|
917
|
-
vec4 pal(int i) { return texelFetch(uPalette, ivec2(i % ${PALETTE_WIDTH}, i / ${PALETTE_WIDTH}), 0); }
|
|
918
|
-
vec4 rampOf(int r) { return texelFetch(uRamps, ivec2(r, 0), 0); }
|
|
919
|
-
bool flashed(int mat, int id) {
|
|
920
|
-
if (uFlash.x <= 0.0) return false;
|
|
921
|
-
if (float(id) >= uFlash.z && float(id) <= uFlash.w) return true;
|
|
922
|
-
for (int k = 0; k < 2; k++) { ivec4 m = uFlashMats[k]; if (m.x == mat || m.y == mat || m.z == mat || m.w == mat) return true; }
|
|
923
|
-
return false;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
void main() {
|
|
927
|
-
ivec2 size = textureSize(uData, 0);
|
|
928
|
-
ivec2 px = ivec2(gl_FragCoord.xy);
|
|
929
|
-
vec2 res = vec2(size);
|
|
930
|
-
// CRT: bend the picture as a tube does -- whole pixels moved, none invented.
|
|
931
|
-
if (uCrt.x > 0.5) {
|
|
932
|
-
vec2 uv = (vec2(px) + 0.5) / res * 2.0 - 1.0;
|
|
933
|
-
uv *= 1.0 + uCrt.y * dot(uv, uv);
|
|
934
|
-
if (abs(uv.x) > 1.0 || abs(uv.y) > 1.0) { outColor = pal(int(uCrt.z)); return; }
|
|
935
|
-
px = clamp(ivec2(floor((uv * 0.5 + 0.5) * res)), ivec2(0), size - 1);
|
|
936
|
-
}
|
|
937
|
-
vec4 d = texelFetch(uData, px, 0);
|
|
938
|
-
vec4 d2 = texelFetch(uData2, px, 0);
|
|
939
|
-
float z = texelFetch(uDepth, px, 0).r;
|
|
940
|
-
int ramp = int(d.g * 255.0 + 0.5);
|
|
941
|
-
int mat = int(d.b * 255.0 + 0.5);
|
|
942
|
-
int id = int(d.a * 255.0 + 0.5);
|
|
943
|
-
float s = screenAt(ivec2(gl_FragCoord.xy)); // (the screen stays put on the glass, under a CRT's bend too)
|
|
944
|
-
float L = d.r;
|
|
945
|
-
|
|
946
|
-
// Colour grading: each ramp swapped for its graded one (day / dusk / night), and shifted along it.
|
|
947
|
-
if (uGrade.x > 0.5) { float g = texelFetch(uRamps, ivec2(ramp, 1), 0).x; if (g >= 0.0) ramp = int(g); }
|
|
948
|
-
// Fog: past its near, pixels go over to the fog's ramp -- the screen decides which, so the edge is dithered.
|
|
949
|
-
if (uFog.x > 0.5 && id != 255) {
|
|
950
|
-
float dist = z * ${FAR.toFixed(1)}; // (depth is the ray's length over FAR)
|
|
951
|
-
float f = clamp((dist - uFog.y) / max(uFog.z - uFog.y, 1e-3), 0.0, 1.0) * uFog.w;
|
|
952
|
-
if (f > s) { ramp = int(uFogLook.x); L = mix(L, uFogLook.y, f); }
|
|
953
|
-
}
|
|
954
|
-
vec4 R = rampOf(ramp);
|
|
955
|
-
int base = int(R.x);
|
|
956
|
-
int len = max(int(R.y), 1);
|
|
957
|
-
float top = float(len - 1);
|
|
958
|
-
float x = L * top;
|
|
959
|
-
if (uGrade.x > 0.5) x += uGrade.y;
|
|
960
|
-
|
|
961
|
-
// Glow: what glows climbs its ramp; round it, a halo climbs the ramps of what's near (or wears the glow's).
|
|
962
|
-
if (uGlow.x > 0.5) {
|
|
963
|
-
if (d2.r >= uGlowK.x) x += uGlow.w * d2.r;
|
|
964
|
-
else {
|
|
965
|
-
float best = 0.0;
|
|
966
|
-
int bestRamp = -1;
|
|
967
|
-
float r = uGlow.y;
|
|
968
|
-
// (A golden-angle spiral of taps covers the disc evenly: about one tap per 1.5 px square at the largest radius.)
|
|
969
|
-
int taps = int(clamp(r * r * 1.4, 8.0, 48.0));
|
|
970
|
-
for (int k = 0; k < 48; k++) {
|
|
971
|
-
if (k >= taps) break;
|
|
972
|
-
float a = float(k) * 2.3999632;
|
|
973
|
-
float rr = max(1.0, r * sqrt((float(k) + 0.5) / float(taps)));
|
|
974
|
-
ivec2 q = clamp(px + ivec2(round(vec2(cos(a), sin(a)) * rr)), ivec2(0), size - 1);
|
|
975
|
-
vec4 o2 = texelFetch(uData2, q, 0);
|
|
976
|
-
if (o2.r >= uGlowK.x) {
|
|
977
|
-
float f = rr / (r + 1.0);
|
|
978
|
-
float w = smoothstep(uGlowK.x, uGlowK.x + 0.3, o2.r) * (1.0 - f * f); // (strong glows reach full halo; it falls off outward)
|
|
979
|
-
if (w > best) { best = w; bestRamp = int(texelFetch(uData, q, 0).g * 255.0 + 0.5); }
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
if (best > 0.0) {
|
|
983
|
-
if (uGlowK.y > 0.5 && best * 1.6 > s) {
|
|
984
|
-
ramp = bestRamp; R = rampOf(ramp); base = int(R.x); len = max(int(R.y), 1); top = float(len - 1);
|
|
985
|
-
x = (0.35 + 0.4 * best) * top;
|
|
986
|
-
} else x += uGlow.z * best;
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
// Rim light: a thing's edge on the light's side, where what's beyond it is far behind, climbs its ramp.
|
|
991
|
-
if (uRim.x > 0.5 && id < 253) {
|
|
992
|
-
ivec2 q = clamp(px + ivec2(round(uRimDir * uRim.y)), ivec2(0), size - 1);
|
|
993
|
-
float oz = texelFetch(uDepth, q, 0).r;
|
|
994
|
-
// (Only a silhouette: another thing, well behind -- a floor seen edge-on is one thing, and gets none.)
|
|
995
|
-
if (abs(texelFetch(uData, q, 0).a - d.a) > 0.5 / 255.0 && oz > z + 0.004) x += uRim.z;
|
|
996
|
-
}
|
|
997
|
-
// Hit flash: the struck thing (by material, or by id) goes up toward white (or onto a flash ramp).
|
|
998
|
-
if (flashed(mat, id)) {
|
|
999
|
-
if (uFlash.y >= 0.0) { ramp = int(uFlash.y); R = rampOf(ramp); base = int(R.x); len = max(int(R.y), 1); top = float(len - 1); x = mix(L * top, top, uFlash.x); }
|
|
1000
|
-
else x = mix(x, top + 0.49, uFlash.x);
|
|
1001
|
-
}
|
|
1002
|
-
// Vignette: the corners step down their ramps.
|
|
1003
|
-
if (uVig.x > 0.5) {
|
|
1004
|
-
vec2 uv = gl_FragCoord.xy / res * 2.0 - 1.0;
|
|
1005
|
-
float r = length(uv * vec2(res.x / res.y, 1.0)) / length(vec2(res.x / res.y, 1.0));
|
|
1006
|
-
x -= smoothstep(uVig.y, uVig.z, r) * uVig.w;
|
|
1007
|
-
}
|
|
1008
|
-
// Scanlines: every so many rows, a step down.
|
|
1009
|
-
if (uScan.x > 0.5 && float(int(gl_FragCoord.y) % int(uScan.y)) < uScan.z) x -= uScan.w;
|
|
1010
|
-
|
|
1011
|
-
// The outline: a thing's edge against what's behind it (mode 1), or only across a gap (2: outer only).
|
|
1012
|
-
bool edge = false;
|
|
1013
|
-
if (uOutline.x > 0.5 && d.a < 0.99) {
|
|
1014
|
-
float gap = uOutline.z; // (in depth: metres / FAR)
|
|
1015
|
-
for (int k = 0; k < 4; k++) {
|
|
1016
|
-
ivec2 o = k == 0 ? ivec2(1, 0) : k == 1 ? ivec2(-1, 0) : k == 2 ? ivec2(0, 1) : ivec2(0, -1);
|
|
1017
|
-
ivec2 q = clamp(px + o, ivec2(0), size - 1);
|
|
1018
|
-
float other = texelFetch(uData, q, 0).a;
|
|
1019
|
-
float oz = texelFetch(uDepth, q, 0).r;
|
|
1020
|
-
if (abs(other - d.a) > 0.5 / 255.0 && oz > z + gap) edge = true;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
if (edge && uOutlineInk >= 0) { outColor = pal(uOutlineInk); return; }
|
|
1024
|
-
|
|
1025
|
-
// To an entry: the screen breaks the step between two.
|
|
1026
|
-
float th = uScreen == 0 ? 0.0 : s - 0.5;
|
|
1027
|
-
x = x + th * uDither;
|
|
1028
|
-
int idx = clamp(int(floor(x + 0.5)), 0, len - 1);
|
|
1029
|
-
if (edge) idx = max(0, idx - int(uOutline.y));
|
|
1030
|
-
// Palette cycling: a ramp's upper entries turn over, so water shimmers and neon runs.
|
|
1031
|
-
else if (uCycle == 1 && R.w > 0.0 && idx >= int(R.z)) { int from = int(R.z); idx = from + (idx - from + int(floor(uTime * R.w))) % (len - from); }
|
|
1032
|
-
outColor = pal(base + idx);
|
|
1033
|
-
}`;
|
|
1034
|
-
|
|
1035
|
-
// ../../../../../../keel-engine/packages/render/src/indexed.ts
|
|
1036
|
-
function edit(src, find, replace) {
|
|
1037
|
-
if (!src.includes(find)) throw new Error(`BAKE_WORLD_FS: WORLD_FS no longer contains ${JSON.stringify(find.slice(0, 60))}`);
|
|
1038
|
-
return src.replace(find, replace);
|
|
1039
|
-
}
|
|
1040
|
-
var SURFACE_UV = `// Where on its part a point is, 0..1 each way: a capsule's (round it, along it -- caps included); a ball's
|
|
1041
|
-
// (longitude round y, latitude up it); a box's or a wedge's (across the face it's on).
|
|
1042
|
-
vec2 surfaceUv(vec3 p, int k) {
|
|
1043
|
-
if (k >= 100 && k < 200) {
|
|
1044
|
-
int i = k - 100;
|
|
1045
|
-
vec4 A = uCap[2 * i]; vec4 B = uCap[2 * i + 1];
|
|
1046
|
-
vec3 ba = B.xyz - A.xyz;
|
|
1047
|
-
vec3 pa = p - A.xyz;
|
|
1048
|
-
float L2 = dot(ba, ba);
|
|
1049
|
-
if (L2 < 1e-8) { vec3 d = normalize(pa + vec3(0.0, 0.0, 1e-6)); return vec2(atan(d.x, d.z) / 6.2831853 + 0.5, d.y * 0.5 + 0.5); }
|
|
1050
|
-
float len = sqrt(L2);
|
|
1051
|
-
vec3 ax = ba / len;
|
|
1052
|
-
float h = dot(pa, ax);
|
|
1053
|
-
vec3 rad = pa - ax * clamp(h, 0.0, len);
|
|
1054
|
-
vec3 e1 = normalize(abs(ax.y) < 0.9 ? cross(ax, vec3(0.0, 1.0, 0.0)) : cross(ax, vec3(1.0, 0.0, 0.0)));
|
|
1055
|
-
vec3 e2 = cross(ax, e1);
|
|
1056
|
-
return vec2(atan(dot(rad, e2), dot(rad, e1)) / 6.2831853 + 0.5, clamp((h + A.w) / (len + 2.0 * A.w), 0.0, 1.0));
|
|
1057
|
-
}
|
|
1058
|
-
vec4 A; vec4 B;
|
|
1059
|
-
if (k < 100) { A = uBox[2 * k]; B = uBox[2 * k + 1]; } else { A = uWedge[3 * (k - 200)]; B = uWedge[3 * (k - 200) + 1]; }
|
|
1060
|
-
vec3 q = p - A.xyz;
|
|
1061
|
-
float c = cos(B.w), s = sin(B.w);
|
|
1062
|
-
q.xz = mat2(c, s, -s, c) * q.xz;
|
|
1063
|
-
vec3 r = q / max(B.xyz, vec3(1e-4));
|
|
1064
|
-
vec3 a = abs(r);
|
|
1065
|
-
vec2 f = a.x >= a.y && a.x >= a.z ? r.zy : a.y >= a.z ? r.xz : r.xy;
|
|
1066
|
-
return clamp(f * 0.5 + 0.5, 0.0, 1.0);
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
void main() {`;
|
|
1070
|
-
var BAKE_WORLD_FS = [
|
|
1071
|
-
["void main() {", `uniform vec4 uSplit; // a point (xyz) the hits are split at, seen from the camera; w 1 on, 0 off
|
|
1072
|
-
uniform float uOrthoH; // > 0: an ORTHOGRAPHIC bake -- parallel rays over a picture this many metres half-high (the game's own view: nothing nearer the camera drawn bigger)
|
|
1073
|
-
${SURFACE_UV}`],
|
|
1074
|
-
["vec3 rd = normalize(uFwd + uv.x * uTan * aspect * uRight + uv.y * uTan * uUp);\n vec3 ro = uEye;", "vec3 rd = uOrthoH > 0.0 ? uFwd : normalize(uFwd + uv.x * uTan * aspect * uRight + uv.y * uTan * uUp);\n vec3 ro = uOrthoH > 0.0 ? uEye + (uv.x * aspect * uRight + uv.y * uUp) * uOrthoH : uEye;"],
|
|
1075
|
-
["float L; float ramp; float id; float depth; float mat; float glow = 0.0; float facing = 1.0;", "float L; float ramp; float id; float depth; float mat; float glow = 0.0; float facing = 1.0; vec2 surf = vec2(0.0);"],
|
|
1076
|
-
["glow = clamp(mr.w, 0.0, 1.0); facing = clamp(dot(n, -rd), 0.0, 1.0);", "glow = clamp(mr.w, 0.0, 1.0); facing = clamp(dot(n, -rd), 0.0, 1.0); surf = surfaceUv(p, int(hit.z + 0.5)); ramp = uSplit.w > 0.5 && dot(p - uSplit.xyz, uFwd) > 0.0 ? 1.0 : 0.0;"],
|
|
1077
|
-
["outData2 = vec4(glow, facing, 0.0, 0.0);", "outData2 = vec4(glow, facing, surf);"]
|
|
1078
|
-
].reduce((src, [find, replace]) => edit(src, find, replace), WORLD_FS);
|
|
1079
|
-
var INDEX_FS = `#version 300 es
|
|
1080
|
-
precision highp float;
|
|
1081
|
-
precision highp sampler2D;
|
|
1082
|
-
uniform sampler2D uData;
|
|
1083
|
-
uniform sampler2D uData2;
|
|
1084
|
-
uniform sampler2D uDepth;
|
|
1085
|
-
uniform float uGap; // the outline's depth gap (metres / FAR), as the pixel pass's uOutline.z
|
|
1086
|
-
out vec4 outColor;
|
|
1087
|
-
void main() {
|
|
1088
|
-
ivec2 size = textureSize(uData, 0);
|
|
1089
|
-
ivec2 px = ivec2(gl_FragCoord.xy);
|
|
1090
|
-
vec4 d = texelFetch(uData, px, 0);
|
|
1091
|
-
int id = int(d.a * 255.0 + 0.5);
|
|
1092
|
-
if (id >= 253) { outColor = vec4(0.0); return; }
|
|
1093
|
-
vec4 d2 = texelFetch(uData2, px, 0);
|
|
1094
|
-
float z = texelFetch(uDepth, px, 0).r;
|
|
1095
|
-
bool edge = false;
|
|
1096
|
-
for (int k = 0; k < 4; k++) {
|
|
1097
|
-
ivec2 o = k == 0 ? ivec2(1, 0) : k == 1 ? ivec2(-1, 0) : k == 2 ? ivec2(0, 1) : ivec2(0, -1);
|
|
1098
|
-
ivec2 q = clamp(px + o, ivec2(0), size - 1);
|
|
1099
|
-
float other = texelFetch(uData, q, 0).a;
|
|
1100
|
-
float oz = texelFetch(uDepth, q, 0).r;
|
|
1101
|
-
if (abs(other - d.a) > 0.5 / 255.0 && oz > z + uGap) edge = true;
|
|
1102
|
-
}
|
|
1103
|
-
int mat = min(int(d.b * 255.0 + 0.5), 31);
|
|
1104
|
-
bool behind = d.g > 0.5 / 255.0;
|
|
1105
|
-
outColor = vec4(float(128 + (edge ? 64 : 0) + (behind ? 32 : 0) + mat) / 255.0, d.r, d2.b, d2.a);
|
|
1106
|
-
}`;
|
|
1107
|
-
var HEIGHT_FS = `${WORLD_FS.slice(0, WORLD_FS.indexOf("void main() {"))}
|
|
1108
|
-
uniform sampler2D uData;
|
|
1109
|
-
uniform sampler2D uDepth;
|
|
1110
|
-
uniform float uScale; // texels a metre (the bake's pixels per metre)
|
|
1111
|
-
uniform float uEps; // how far the bake thinned each solid (metres)
|
|
1112
|
-
uniform float uOrthoH; // > 0: an orthographic bake (as pass 1's)
|
|
1113
|
-
void main() {
|
|
1114
|
-
ivec2 px = ivec2(gl_FragCoord.xy);
|
|
1115
|
-
int id = int(texelFetch(uData, px, 0).a * 255.0 + 0.5);
|
|
1116
|
-
if (id >= 253) { outData = vec4(0.0); outData2 = vec4(0.0); return; }
|
|
1117
|
-
float t = texelFetch(uDepth, px, 0).r * FAR;
|
|
1118
|
-
vec2 uv = (gl_FragCoord.xy / uRes) * 2.0 - 1.0;
|
|
1119
|
-
vec3 rd = uOrthoH > 0.0 ? uFwd : normalize(uFwd + uv.x * uTan * (uRes.x / uRes.y) * uRight + uv.y * uTan * uUp);
|
|
1120
|
-
vec3 ro = uOrthoH > 0.0 ? uEye + (uv.x * (uRes.x / uRes.y) * uRight + uv.y * uUp) * uOrthoH : uEye;
|
|
1121
|
-
// (Back off past where the march could have overshot, then march onto the shell pass 1 calls a hit -- the thinned
|
|
1122
|
-
// field within its hit tolerance, 0.0015 x the ray's length: at the bake's distance, uEps, the design's own
|
|
1123
|
-
// surface -- the first point along the ray the colours were drawn from, never past it.)
|
|
1124
|
-
float s = max(0.0, t - 3.0 * max(uEps, 0.0015 * t) - 0.002);
|
|
1125
|
-
for (int i = 0; i < 48; i++) {
|
|
1126
|
-
float d = map(ro + rd * s).x - 0.0015 * s;
|
|
1127
|
-
if (d < 0.0002) break;
|
|
1128
|
-
s += d;
|
|
1129
|
-
if (s > t) { s = t; break; }
|
|
1130
|
-
}
|
|
1131
|
-
float y = max(ro.y + rd.y * s, 0.0);
|
|
1132
|
-
uint v = uint(clamp(floor(y * uScale * 16.0 + 1.5), 1.0, 65535.0));
|
|
1133
|
-
outData = vec4(float(v >> 8u) / 255.0, float(v & 255u) / 255.0, 0.0, 1.0);
|
|
1134
|
-
outData2 = vec4(0.0);
|
|
1135
|
-
}`;
|
|
1136
|
-
var DEPTH_FS = `#version 300 es
|
|
1137
|
-
precision highp float;
|
|
1138
|
-
precision highp sampler2D;
|
|
1139
|
-
uniform sampler2D uDepth;
|
|
1140
|
-
out vec4 outColor;
|
|
1141
|
-
void main() {
|
|
1142
|
-
float z = texelFetch(uDepth, ivec2(gl_FragCoord.xy), 0).r;
|
|
1143
|
-
uint v = uint(clamp(z, 0.0, 1.0) * 16777215.0 + 0.5);
|
|
1144
|
-
outColor = vec4(float(v >> 16u) / 255.0, float((v >> 8u) & 255u) / 255.0, float(v & 255u) / 255.0, 1.0);
|
|
1145
|
-
}`;
|
|
1146
|
-
var unpackDepth = (r, g, b) => (r << 16 | g << 8 | b) / 16777215;
|
|
1147
|
-
|
|
1148
|
-
// ../../../../../../keel-engine/packages/render/src/raster.ts
|
|
1149
|
-
var RASTER_FLOATS = 16;
|
|
1150
|
-
var DIRECT_MAT = 254;
|
|
1151
|
-
function boxTemplate() {
|
|
1152
|
-
const faces = [
|
|
1153
|
-
// normal, u axis, v axis (u x v = normal: counter-clockwise from outside)
|
|
1154
|
-
[[1, 0, 0], [0, 0, -1], [0, 1, 0]],
|
|
1155
|
-
[[-1, 0, 0], [0, 0, 1], [0, 1, 0]],
|
|
1156
|
-
[[0, 1, 0], [1, 0, 0], [0, 0, -1]],
|
|
1157
|
-
[[0, -1, 0], [1, 0, 0], [0, 0, 1]],
|
|
1158
|
-
[[0, 0, 1], [1, 0, 0], [0, 1, 0]],
|
|
1159
|
-
[[0, 0, -1], [-1, 0, 0], [0, 1, 0]]
|
|
1160
|
-
];
|
|
1161
|
-
const verts = [], norms = [], index = [];
|
|
1162
|
-
faces.forEach(([n, u, v], f) => {
|
|
1163
|
-
for (const [a, b] of [[-1, -1], [1, -1], [1, 1], [-1, 1]]) {
|
|
1164
|
-
verts.push(n[0] + u[0] * a + v[0] * b, n[1] + u[1] * a + v[1] * b, n[2] + u[2] * a + v[2] * b);
|
|
1165
|
-
norms.push(n[0], n[1], n[2]);
|
|
1166
|
-
}
|
|
1167
|
-
index.push(f * 4, f * 4 + 1, f * 4 + 2, f * 4, f * 4 + 2, f * 4 + 3);
|
|
1168
|
-
});
|
|
1169
|
-
return { verts: Float32Array.from(verts), norms: Float32Array.from(norms), index: Uint16Array.from(index) };
|
|
1170
|
-
}
|
|
1171
|
-
function capsuleTemplate(around = 10, rings = 3) {
|
|
1172
|
-
const verts = [], norms = [], index = [];
|
|
1173
|
-
const row = around + 1;
|
|
1174
|
-
const rows = [];
|
|
1175
|
-
for (let k = 0; k <= rings; k += 1) rows.push([-Math.PI / 2 + k / rings * (Math.PI / 2), 0]);
|
|
1176
|
-
for (let k = 0; k <= rings; k += 1) rows.push([k / rings * (Math.PI / 2), 1]);
|
|
1177
|
-
for (const [phi, end] of rows) for (let i = 0; i <= around; i += 1) {
|
|
1178
|
-
const th = i / around * Math.PI * 2;
|
|
1179
|
-
verts.push(Math.cos(th), Math.sin(th), Math.sin(phi));
|
|
1180
|
-
norms.push(Math.cos(phi), end, 0);
|
|
1181
|
-
}
|
|
1182
|
-
for (let r = 0; r < rows.length - 1; r += 1) for (let i = 0; i < around; i += 1) {
|
|
1183
|
-
const a = r * row + i, b = a + 1, c = a + row, d = c + 1;
|
|
1184
|
-
index.push(a, c, b, b, c, d);
|
|
1185
|
-
}
|
|
1186
|
-
return { verts: Float32Array.from(verts), norms: Float32Array.from(norms), index: Uint16Array.from(index) };
|
|
1187
|
-
}
|
|
1188
|
-
var FARS = FAR.toFixed(1);
|
|
1189
|
-
var RASTER_VS = `#version 300 es
|
|
1190
|
-
layout(location=0) in vec3 aVert;
|
|
1191
|
-
layout(location=1) in vec3 aNorm;
|
|
1192
|
-
layout(location=2) in vec4 aI0;
|
|
1193
|
-
layout(location=3) in vec4 aI1;
|
|
1194
|
-
layout(location=4) in vec4 aI2;
|
|
1195
|
-
layout(location=5) in vec4 aI3;
|
|
1196
|
-
uniform int uKind;
|
|
1197
|
-
uniform vec3 uEye, uFwd, uRight, uUp;
|
|
1198
|
-
uniform float uTan, uAspect;
|
|
1199
|
-
out vec3 vPos;
|
|
1200
|
-
out vec3 vNorm;
|
|
1201
|
-
flat out vec4 vLook; // material, ramp (-1: the material's), light scale, id
|
|
1202
|
-
flat out vec4 vFx; // fade, glow, 0, 0
|
|
1203
|
-
out float vShade; // a mesh vertex's lightness offset
|
|
1204
|
-
// (Own frame -> world, the inverse of WORLD_FS's world -> box turn: x = c x' + s z', z = -s x' + c z'.)
|
|
1205
|
-
vec3 turn(vec3 q, float yaw) { float c = cos(yaw), s = sin(yaw); return vec3(c * q.x + s * q.z, q.y, -s * q.x + c * q.z); }
|
|
1206
|
-
void main() {
|
|
1207
|
-
vec3 p; vec3 n;
|
|
1208
|
-
vShade = 0.0;
|
|
1209
|
-
if (uKind == 0) {
|
|
1210
|
-
p = aVert; n = aNorm;
|
|
1211
|
-
vLook = vec4(aI0.x, aI0.y, 1.0, aI0.w); vFx = vec4(1.0, 0.0, 0.0, 0.0); vShade = aI0.z;
|
|
1212
|
-
} else if (uKind == 3) {
|
|
1213
|
-
vec3 a = aI0.xyz, b = aI1.xyz;
|
|
1214
|
-
float r = aI1.w;
|
|
1215
|
-
vec3 ax = b - a;
|
|
1216
|
-
float L = length(ax);
|
|
1217
|
-
ax = L > 1e-6 ? ax / L : vec3(0.0, 1.0, 0.0);
|
|
1218
|
-
vec3 u = normalize(abs(ax.y) < 0.9 ? cross(ax, vec3(0.0, 1.0, 0.0)) : cross(ax, vec3(1.0, 0.0, 0.0)));
|
|
1219
|
-
vec3 v = cross(ax, u);
|
|
1220
|
-
float sp = aVert.z, cp = aNorm.x;
|
|
1221
|
-
bool atB = aNorm.y > 0.5;
|
|
1222
|
-
// (phi runs from a's pole -- back along the axis, sin phi = -1 -- through the two equators to b's.)
|
|
1223
|
-
vec3 d = cp * (aVert.x * u + aVert.y * v) + sp * ax;
|
|
1224
|
-
p = (atB ? b : a) + r * d;
|
|
1225
|
-
n = d;
|
|
1226
|
-
vLook = vec4(aI0.w, aI2.y, aI2.z, aI2.w); vFx = aI3;
|
|
1227
|
-
} else {
|
|
1228
|
-
vec3 h = aI1.xyz;
|
|
1229
|
-
vec3 q = aVert;
|
|
1230
|
-
vec3 nq = aNorm;
|
|
1231
|
-
if (uKind == 2) {
|
|
1232
|
-
float lo = aI2.x;
|
|
1233
|
-
// (The +z edge of the top comes down to the foot: lo x its height.)
|
|
1234
|
-
if (q.y > 0.5 && q.z > 0.5) q.y = -1.0 + 2.0 * lo;
|
|
1235
|
-
if (nq.y > 0.5) nq = normalize(vec3(0.0, 2.0 * h.z, 2.0 * h.y * (1.0 - lo)));
|
|
1236
|
-
}
|
|
1237
|
-
p = aI0.xyz + turn(q * h, aI1.w);
|
|
1238
|
-
n = turn(uKind == 2 ? nq : aNorm, aI1.w);
|
|
1239
|
-
vLook = vec4(aI0.w, aI2.y, aI2.z, aI2.w); vFx = aI3;
|
|
1240
|
-
}
|
|
1241
|
-
vPos = p; vNorm = n;
|
|
1242
|
-
vec3 e = p - uEye;
|
|
1243
|
-
float z = dot(e, uFwd);
|
|
1244
|
-
const float NEAR = 0.05, FARZ = ${FARS} * 1.5;
|
|
1245
|
-
gl_Position = vec4(dot(e, uRight) / (uTan * uAspect), dot(e, uUp) / uTan, z * (FARZ + NEAR) / (FARZ - NEAR) - 2.0 * FARZ * NEAR / (FARZ - NEAR), z);
|
|
1246
|
-
}`;
|
|
1247
|
-
var RASTER_FS = `#version 300 es
|
|
1248
|
-
precision highp float;
|
|
1249
|
-
precision highp sampler2D;
|
|
1250
|
-
uniform sampler2D uMats;
|
|
1251
|
-
uniform vec3 uEye, uSun;
|
|
1252
|
-
uniform float uWaterY, uFogNear, uFogFar, uTan, uTime;
|
|
1253
|
-
uniform vec2 uRes;
|
|
1254
|
-
in vec3 vPos;
|
|
1255
|
-
in vec3 vNorm;
|
|
1256
|
-
flat in vec4 vLook;
|
|
1257
|
-
flat in vec4 vFx;
|
|
1258
|
-
in float vShade;
|
|
1259
|
-
layout(location = 0) out vec4 outData;
|
|
1260
|
-
layout(location = 1) out vec4 outData2;
|
|
1261
|
-
float bayer4(ivec2 p) { int m[16] = int[16](0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5); return (float(m[(p.y & 3) * 4 + (p.x & 3)]) + 0.5) / 16.0; }
|
|
1262
|
-
vec4 matOf(int i) { return texelFetch(uMats, ivec2(i, 0), 0); }
|
|
1263
|
-
float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
|
|
1264
|
-
float vnoise(vec2 p) { vec2 i = floor(p), f = fract(p); f = f * f * (3.0 - 2.0 * f); return mix(mix(hash(i), hash(i + vec2(1, 0)), f.x), mix(hash(i + vec2(0, 1)), hash(i + vec2(1, 1)), f.x), f.y); }
|
|
1265
|
-
// (Engine patterns past the checker: 2 grain -- a ground's speckle, blades and pebbles at a pixel or two -- and 3
|
|
1266
|
-
// strata -- a cliff's bands. Both fade out where a cell would be under a pixel, as the checker does.)
|
|
1267
|
-
float grain(vec3 p, vec3 n, float px) {
|
|
1268
|
-
vec2 uv = abs(n.y) > 0.6 ? p.xz : abs(n.x) > abs(n.z) ? p.zy : p.xy;
|
|
1269
|
-
float fine = hash(floor(uv * 7.0)) - 0.5, coarse = vnoise(uv * 0.9) - 0.5;
|
|
1270
|
-
return (fine * 0.12 * smoothstep(1.2, 3.0, px / 7.0) + coarse * 0.14);
|
|
1271
|
-
}
|
|
1272
|
-
float strata(vec3 p, float px) { return (vnoise(vec2(p.x * 0.7 + p.z * 0.7, p.y * 3.1)) - 0.5) * 0.2 + (hash(floor(vec2(p.x + p.z, p.y) * 5.0)) - 0.5) * 0.07 * smoothstep(1.2, 3.0, px / 5.0); }
|
|
1273
|
-
float tiles(vec3 p, vec3 n) {
|
|
1274
|
-
vec2 uv = abs(n.y) > 0.6 ? p.xz : abs(n.x) > abs(n.z) ? p.zy : p.xy;
|
|
1275
|
-
vec2 f = fract(uv);
|
|
1276
|
-
float joint = step(0.95, max(f.x, f.y)) * 0.06;
|
|
1277
|
-
float chk = mod(floor(uv.x) + floor(uv.y), 2.0);
|
|
1278
|
-
return 0.16 * chk - 0.08 - joint;
|
|
1279
|
-
}
|
|
1280
|
-
void main() {
|
|
1281
|
-
// (A dissolve: the solid keeps the screen's lower thresholds, a sprite fading in keeps the rest.)
|
|
1282
|
-
if (vFx.x < 0.999 && bayer4(ivec2(gl_FragCoord.xy)) >= vFx.x) discard;
|
|
1283
|
-
vec3 n = normalize(vNorm);
|
|
1284
|
-
vec3 e = vPos - uEye;
|
|
1285
|
-
float dist = length(e);
|
|
1286
|
-
vec3 rd = e / max(dist, 1e-6);
|
|
1287
|
-
if (dot(n, rd) > 0.0) n = -n; // (a face seen from behind: light it as its front)
|
|
1288
|
-
int mi = int(vLook.x + 0.5);
|
|
1289
|
-
vec4 mr = matOf(mi);
|
|
1290
|
-
float ramp = vLook.y >= 0.0 ? vLook.y : mr.x;
|
|
1291
|
-
float L;
|
|
1292
|
-
float tilePx = uRes.y / max(dist * 2.0 * uTan, 1e-3); // (picture pixels a metre here)
|
|
1293
|
-
if (mi == ${WATER_MAT}) {
|
|
1294
|
-
// (Water as a surface: dark and deep, rippled, brighter toward the eye's grazing angle, a glint here and there.)
|
|
1295
|
-
float ripple = vnoise(vPos.xz * 1.3 + vec2(uTime * 0.35, uTime * 0.22)) * vnoise(vPos.xz * 0.6 - vec2(uTime * 0.1, 0.0));
|
|
1296
|
-
float glint = step(0.8, vnoise(vPos.xz * 4.0 + uTime * 0.7)) * 0.25 * clamp(1.0 - dist / 40.0, 0.0, 1.0);
|
|
1297
|
-
L = 0.14 + 0.22 * ripple + 0.2 * pow(1.0 - clamp(-rd.y, 0.0, 1.0), 3.0) + glint + vShade;
|
|
1298
|
-
} else {
|
|
1299
|
-
float diff = max(dot(n, uSun), 0.0);
|
|
1300
|
-
float wglow = clamp(1.0 - (vPos.y - uWaterY) / 1.6, 0.0, 1.0) * max(0.0, -n.y * 0.2 + 0.8) * 0.35;
|
|
1301
|
-
L = (0.16 + 0.1 * n.y + 0.5 * diff) * mr.y * vLook.z + wglow + mr.w + vFx.y + vShade;
|
|
1302
|
-
float patternK = smoothstep(2.5, 7.0, tilePx) * (0.35 + 0.65 * smoothstep(28.0, 96.0, uRes.y));
|
|
1303
|
-
if (mr.z > 0.5 && mr.z < 1.5) L += tiles(vPos, n) * patternK;
|
|
1304
|
-
else if (mr.z > 1.5 && mr.z < 2.5) L += grain(vPos, n, tilePx);
|
|
1305
|
-
else if (mr.z > 2.5 && mr.z < 3.5) L += strata(vPos, tilePx);
|
|
1306
|
-
}
|
|
1307
|
-
L = mix(L, 0.12, clamp((dist - uFogNear) / (uFogFar - uFogNear), 0.0, 1.0));
|
|
1308
|
-
outData = vec4(clamp(L, 0.0, 1.0), ramp / 255.0, float(mi) / 255.0, mod(vLook.w, 250.0) / 255.0);
|
|
1309
|
-
outData2 = vec4(clamp(mr.w + vFx.y, 0.0, 1.0), clamp(dot(n, -rd), 0.0, 1.0), 0.0, 0.0);
|
|
1310
|
-
gl_FragDepth = clamp(dist / ${FARS}, 0.0, 1.0);
|
|
1311
|
-
}`;
|
|
1312
|
-
var DEPTH_OUT_FS = `#version 300 es
|
|
1313
|
-
precision highp float;
|
|
1314
|
-
precision highp sampler2D;
|
|
1315
|
-
uniform sampler2D uDepth;
|
|
1316
|
-
uniform vec2 uMap; // scale, bias
|
|
1317
|
-
out vec4 outColor;
|
|
1318
|
-
void main() {
|
|
1319
|
-
gl_FragDepth = clamp(texelFetch(uDepth, ivec2(gl_FragCoord.xy), 0).r * uMap.x + uMap.y, 0.0, 1.0);
|
|
1320
|
-
outColor = vec4(0.0);
|
|
1321
|
-
}`;
|
|
1322
|
-
var DIRECT_PIXEL_FS = PIXEL_FS.replace(" float L = d.r;\n", ` float L = d.r;
|
|
1323
|
-
// (Engine, DIRECT_MAT: a palette index straight -- its ramp's base, length and place ride along for the outline and fog.)
|
|
1324
|
-
int directBase = -1, directLen = 1, directPos = 0;
|
|
1325
|
-
if (mat == ${DIRECT_MAT} && d2.a > 0.0) {
|
|
1326
|
-
int pl = int(d2.a * 255.0 + 0.5);
|
|
1327
|
-
directPos = pl & 15;
|
|
1328
|
-
directLen = max(1, pl >> 4);
|
|
1329
|
-
directBase = int(d.r * 255.0 + 0.5) + int(d.g * 255.0 + 0.5) * 256 - directPos;
|
|
1330
|
-
L = d2.b;
|
|
1331
|
-
}
|
|
1332
|
-
`).replace(" if (uGrade.x > 0.5) { float g", " if (uGrade.x > 0.5 && directBase < 0) { float g").replace(" if (f > s) { ramp = int(uFogLook.x); L = mix(L, uFogLook.y, f); }\n }\n vec4 R = rampOf(ramp);", " if (f > s) { ramp = int(uFogLook.x); L = mix(L, uFogLook.y, f); directBase = -1; }\n }\n vec4 R = directBase >= 0 ? vec4(float(directBase), float(directLen), 0.0, 0.0) : rampOf(ramp);").replace(" float x = L * top;\n", " float x = directBase >= 0 ? float(directPos) : L * top;\n");
|
|
1333
|
-
if (!DIRECT_PIXEL_FS.includes("directBase = -1; }") || !DIRECT_PIXEL_FS.includes("float(directPos) : L * top") || !DIRECT_PIXEL_FS.includes("directBase < 0) { float g") || !DIRECT_PIXEL_FS.includes("directPos = pl & 15")) throw new Error("DIRECT_PIXEL_FS: PIXEL_FS changed under its edits.");
|
|
1334
|
-
|
|
1335
|
-
// ../../../../../../keel-engine/packages/render/src/smooth.ts
|
|
1336
|
-
function smoothPixelFs(fs) {
|
|
1337
|
-
const edits = [
|
|
1338
|
-
["uniform int uCycle; // on\n", "uniform int uCycle; // on\nuniform int uSS; // samples to an art pixel, each way (smooth edges)\n"],
|
|
1339
|
-
["float s = screenAt(ivec2(gl_FragCoord.xy));", "float s = screenAt(ivec2(gl_FragCoord.xy) / uSS);"],
|
|
1340
|
-
["ivec2(round(vec2(cos(a), sin(a)) * rr))", "ivec2(round(vec2(cos(a), sin(a)) * rr * float(uSS)))"],
|
|
1341
|
-
["ivec2(round(uRimDir * uRim.y))", "ivec2(round(uRimDir * uRim.y * float(uSS)))"],
|
|
1342
|
-
["ivec2 q = clamp(px + o, ivec2(0), size - 1);", "ivec2 q = clamp(px + o * uSS, ivec2(0), size - 1);"],
|
|
1343
|
-
["float(int(gl_FragCoord.y) % int(uScan.y))", "float((int(gl_FragCoord.y) / uSS) % int(uScan.y))"]
|
|
1344
|
-
];
|
|
1345
|
-
let out = fs;
|
|
1346
|
-
for (const [from, to] of edits) {
|
|
1347
|
-
if (!out.includes(from)) throw new Error(`smoothPixelFs: the pixel pass changed under its edit (${from.trim().slice(0, 40)})`);
|
|
1348
|
-
out = out.replace(from, to);
|
|
1349
|
-
}
|
|
1350
|
-
return out;
|
|
1351
|
-
}
|
|
1352
|
-
function smoothDepthOutFs(fs) {
|
|
1353
|
-
const a = "uniform vec2 uMap; // scale, bias\n", b = "texelFetch(uDepth, ivec2(gl_FragCoord.xy), 0)";
|
|
1354
|
-
if (!fs.includes(a) || !fs.includes(b)) throw new Error("smoothDepthOutFs: the depth pass changed under its edit");
|
|
1355
|
-
return fs.replace(a, a + "uniform int uSS;\n").replace(b, "texelFetch(uDepth, ivec2(gl_FragCoord.xy) * uSS + uSS / 2, 0)");
|
|
1356
|
-
}
|
|
1357
|
-
var RESOLVE_FS = `#version 300 es
|
|
1358
|
-
precision highp float;
|
|
1359
|
-
uniform sampler2D uColour;
|
|
1360
|
-
uniform int uSS;
|
|
1361
|
-
out vec4 outColor;
|
|
1362
|
-
void main() {
|
|
1363
|
-
ivec2 p = ivec2(gl_FragCoord.xy) * uSS;
|
|
1364
|
-
vec3 acc = vec3(0.0);
|
|
1365
|
-
for (int y = 0; y < 4; y++) {
|
|
1366
|
-
if (y >= uSS) break;
|
|
1367
|
-
for (int x = 0; x < 4; x++) {
|
|
1368
|
-
if (x >= uSS) break;
|
|
1369
|
-
vec3 c = texelFetch(uColour, p + ivec2(x, y), 0).rgb;
|
|
1370
|
-
acc += c * c;
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
outColor = vec4(sqrt(acc / float(uSS * uSS)), 1.0);
|
|
1374
|
-
}`;
|
|
1375
|
-
function supersampleFor(width, height, asked = "auto", budget = 11e5) {
|
|
1376
|
-
if (asked !== "auto") return Math.max(1, Math.min(4, Math.round(asked)));
|
|
1377
|
-
for (const k of [3, 2]) if (width * height * k * k <= budget) return k;
|
|
1378
|
-
return 1;
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
// ../../../../../../keel-engine/packages/render/src/link-program.ts
|
|
1382
|
-
function linkProgram(gl, vertex, fragment, name = "Program") {
|
|
1383
|
-
const shaders = [];
|
|
1384
|
-
const attached = [];
|
|
1385
|
-
let program2 = null;
|
|
1386
|
-
let linked = false;
|
|
1387
|
-
try {
|
|
1388
|
-
for (const [type, source] of [[gl.VERTEX_SHADER, vertex], [gl.FRAGMENT_SHADER, fragment]]) {
|
|
1389
|
-
const shader = gl.createShader(type);
|
|
1390
|
-
if (!shader) throw new Error(`${name}: shader allocation failed`);
|
|
1391
|
-
shaders.push(shader);
|
|
1392
|
-
gl.shaderSource(shader, source);
|
|
1393
|
-
gl.compileShader(shader);
|
|
1394
|
-
}
|
|
1395
|
-
program2 = gl.createProgram();
|
|
1396
|
-
if (!program2) throw new Error(`${name}: program allocation failed`);
|
|
1397
|
-
for (const shader of shaders) {
|
|
1398
|
-
gl.attachShader(program2, shader);
|
|
1399
|
-
attached.push(shader);
|
|
1400
|
-
}
|
|
1401
|
-
gl.linkProgram(program2);
|
|
1402
|
-
if (!gl.getProgramParameter(program2, gl.LINK_STATUS)) {
|
|
1403
|
-
const errors = [];
|
|
1404
|
-
for (const shader of shaders) {
|
|
1405
|
-
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) errors.push(gl.getShaderInfoLog(shader) || "shader compile failed");
|
|
1406
|
-
}
|
|
1407
|
-
throw new Error(`${name}: ${errors.join("; ") || gl.getProgramInfoLog(program2) || "program link failed"}`);
|
|
1408
|
-
}
|
|
1409
|
-
linked = true;
|
|
1410
|
-
return program2;
|
|
1411
|
-
} finally {
|
|
1412
|
-
if (program2) for (const shader of attached) gl.detachShader(program2, shader);
|
|
1413
|
-
for (const shader of shaders) gl.deleteShader(shader);
|
|
1414
|
-
if (program2 && !linked) gl.deleteProgram(program2);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
// ../../../../../../keel-engine/packages/render/src/program.ts
|
|
1419
|
-
function program(gl, vs, fs) {
|
|
1420
|
-
const p = linkProgram(gl, vs, fs, "Pixel renderer");
|
|
1421
|
-
try {
|
|
1422
|
-
const loc = {};
|
|
1423
|
-
const n = gl.getProgramParameter(p, gl.ACTIVE_UNIFORMS);
|
|
1424
|
-
for (let i = 0; i < n; i += 1) {
|
|
1425
|
-
const u = gl.getActiveUniform(p, i);
|
|
1426
|
-
if (u) loc[u.name.replace(/\[0\]$/, "")] = gl.getUniformLocation(p, u.name);
|
|
1427
|
-
}
|
|
1428
|
-
return { p, loc };
|
|
1429
|
-
} catch (error) {
|
|
1430
|
-
gl.deleteProgram(p);
|
|
1431
|
-
throw error;
|
|
1432
|
-
}
|
|
1433
|
-
}
|
|
1434
|
-
|
|
1435
|
-
// ../../../../../../keel-engine/packages/render/src/indexed-pass.ts
|
|
1436
|
-
var norm2 = (a) => {
|
|
1437
|
-
const l = Math.hypot(a[0], a[1], a[2]) || 1;
|
|
1438
|
-
return [a[0] / l, a[1] / l, a[2] / l];
|
|
1439
|
-
};
|
|
1440
|
-
function createIndexedPass(env, createDepth) {
|
|
1441
|
-
const gl = env.gl;
|
|
1442
|
-
const nearest = () => {
|
|
1443
|
-
for (const [k, v] of [[gl.TEXTURE_MIN_FILTER, gl.NEAREST], [gl.TEXTURE_MAG_FILTER, gl.NEAREST], [gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE], [gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE]]) gl.texParameteri(gl.TEXTURE_2D, k, v);
|
|
1444
|
-
};
|
|
1445
|
-
let extra = null;
|
|
1446
|
-
function bakeMode() {
|
|
1447
|
-
if (!extra) {
|
|
1448
|
-
const bw = program(gl, FULLSCREEN_VS, BAKE_WORLD_FS);
|
|
1449
|
-
["Boxes", "Wedges", "Capsules"].forEach((name, i) => gl.uniformBlockBinding(bw.p, gl.getUniformBlockIndex(bw.p, name), i));
|
|
1450
|
-
extra = { world: bw, index: program(gl, FULLSCREEN_VS, INDEX_FS), depth: createDepth?.() ?? null, height: null, fb: gl.createFramebuffer(), tex: gl.createTexture(), w: 0, h: 0 };
|
|
1451
|
-
}
|
|
1452
|
-
if (extra.w !== env.width || extra.h !== env.height) {
|
|
1453
|
-
gl.bindTexture(gl.TEXTURE_2D, extra.tex);
|
|
1454
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, env.width, env.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
1455
|
-
nearest();
|
|
1456
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, extra.fb);
|
|
1457
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, extra.tex, 0);
|
|
1458
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
1459
|
-
extra.w = env.width;
|
|
1460
|
-
extra.h = env.height;
|
|
1461
|
-
}
|
|
1462
|
-
return extra;
|
|
1463
|
-
}
|
|
1464
|
-
function pass(prog, fb, textures) {
|
|
1465
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
|
|
1466
|
-
gl.viewport(0, 0, env.width, env.height);
|
|
1467
|
-
gl.useProgram(prog);
|
|
1468
|
-
textures.forEach(([t, loc], i) => {
|
|
1469
|
-
gl.activeTexture(gl.TEXTURE0 + i);
|
|
1470
|
-
gl.bindTexture(gl.TEXTURE_2D, t);
|
|
1471
|
-
gl.uniform1i(loc, i);
|
|
1472
|
-
});
|
|
1473
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, env.quad);
|
|
1474
|
-
const a = gl.getAttribLocation(prog, "aPos");
|
|
1475
|
-
gl.enableVertexAttribArray(a);
|
|
1476
|
-
gl.vertexAttribPointer(a, 2, gl.FLOAT, false, 0, 0);
|
|
1477
|
-
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
1478
|
-
}
|
|
1479
|
-
function readTarget(fb, attachment) {
|
|
1480
|
-
const out = new Uint8Array(env.width * env.height * 4);
|
|
1481
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
|
|
1482
|
-
if (fb) gl.readBuffer(attachment);
|
|
1483
|
-
gl.readPixels(0, 0, env.width, env.height, gl.RGBA, gl.UNSIGNED_BYTE, out);
|
|
1484
|
-
if (fb) gl.readBuffer(gl.COLOR_ATTACHMENT0);
|
|
1485
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
1486
|
-
return out;
|
|
1487
|
-
}
|
|
1488
|
-
const api = {
|
|
1489
|
-
renderIndexed({ eye, target: look, fov = 1.2, time = 0, sun = [0.4, 0.8, 0.3], waterY = 0, fogNear = 25, fogFar = 110, gap = 0.56, split, ortho = 0 }) {
|
|
1490
|
-
const x = bakeMode();
|
|
1491
|
-
const { forward: fwd, right, up } = cameraBasis(eye, look);
|
|
1492
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, env.fbo);
|
|
1493
|
-
gl.viewport(0, 0, env.width, env.height);
|
|
1494
|
-
gl.enable(gl.DEPTH_TEST);
|
|
1495
|
-
gl.depthFunc(gl.ALWAYS);
|
|
1496
|
-
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
1497
|
-
const BW = x.world.loc;
|
|
1498
|
-
gl.useProgram(x.world.p);
|
|
1499
|
-
gl.uniform2f(BW.uRes, env.width, env.height);
|
|
1500
|
-
gl.uniform3fv(BW.uEye, eye);
|
|
1501
|
-
gl.uniform3fv(BW.uFwd, fwd);
|
|
1502
|
-
gl.uniform3fv(BW.uRight, right);
|
|
1503
|
-
gl.uniform3fv(BW.uUp, up);
|
|
1504
|
-
gl.uniform1f(BW.uTan, Math.tan(fov / 2));
|
|
1505
|
-
gl.uniform1f(BW.uTime, time);
|
|
1506
|
-
gl.uniform1i(BW.uBoxes, env.counts.boxes);
|
|
1507
|
-
gl.uniform1i(BW.uWedges, env.counts.wedges);
|
|
1508
|
-
gl.uniform1i(BW.uCaps, env.counts.capsules);
|
|
1509
|
-
for (const blk of env.blocks) gl.bindBufferBase(gl.UNIFORM_BUFFER, blk.i, blk.buf);
|
|
1510
|
-
gl.activeTexture(gl.TEXTURE1);
|
|
1511
|
-
gl.bindTexture(gl.TEXTURE_2D, env.matTex);
|
|
1512
|
-
gl.uniform1i(BW.uMats, 1);
|
|
1513
|
-
gl.uniform3fv(BW.uSun, norm2(sun));
|
|
1514
|
-
gl.uniform1f(BW.uWaterY, waterY);
|
|
1515
|
-
gl.uniform1f(BW.uFogNear, fogNear);
|
|
1516
|
-
gl.uniform1f(BW.uFogFar, fogFar);
|
|
1517
|
-
gl.uniform4f(BW.uSplit, split?.[0] ?? 0, split?.[1] ?? 0, split?.[2] ?? 0, split ? 1 : 0);
|
|
1518
|
-
gl.uniform1f(BW.uOrthoH, ortho);
|
|
1519
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, env.quad);
|
|
1520
|
-
const aw = gl.getAttribLocation(x.world.p, "aPos");
|
|
1521
|
-
gl.enableVertexAttribArray(aw);
|
|
1522
|
-
gl.vertexAttribPointer(aw, 2, gl.FLOAT, false, 0, 0);
|
|
1523
|
-
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
1524
|
-
gl.disable(gl.DEPTH_TEST);
|
|
1525
|
-
gl.useProgram(x.index.p);
|
|
1526
|
-
gl.uniform1f(x.index.loc.uGap, gap / FAR);
|
|
1527
|
-
pass(x.index.p, x.fb, [[env.dataTex, x.index.loc.uData], [env.data2Tex, x.index.loc.uData2], [env.depthTex, x.index.loc.uDepth]]);
|
|
1528
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
1529
|
-
return x.fb;
|
|
1530
|
-
},
|
|
1531
|
-
readIndexed() {
|
|
1532
|
-
return readTarget(bakeMode().fb, gl.COLOR_ATTACHMENT0);
|
|
1533
|
-
},
|
|
1534
|
-
renderIndexedHeights({ eye, target: look, fov = 1.2, pixelsPerMetre, eps = 0, ortho = 0 }) {
|
|
1535
|
-
const x = bakeMode();
|
|
1536
|
-
if (!x.height) {
|
|
1537
|
-
x.height = program(gl, FULLSCREEN_VS, HEIGHT_FS);
|
|
1538
|
-
["Boxes", "Wedges", "Capsules"].forEach((name, i) => gl.uniformBlockBinding(x.height.p, gl.getUniformBlockIndex(x.height.p, name), i));
|
|
1539
|
-
}
|
|
1540
|
-
const hp = x.height;
|
|
1541
|
-
const { forward: fwd, right, up } = cameraBasis(eye, look);
|
|
1542
|
-
gl.disable(gl.DEPTH_TEST);
|
|
1543
|
-
gl.useProgram(hp.p);
|
|
1544
|
-
const L = hp.loc;
|
|
1545
|
-
gl.uniform2f(L.uRes, env.width, env.height);
|
|
1546
|
-
gl.uniform3fv(L.uEye, eye);
|
|
1547
|
-
gl.uniform3fv(L.uFwd, fwd);
|
|
1548
|
-
gl.uniform3fv(L.uRight, right);
|
|
1549
|
-
gl.uniform3fv(L.uUp, up);
|
|
1550
|
-
gl.uniform1f(L.uTan, Math.tan(fov / 2));
|
|
1551
|
-
gl.uniform1f(L.uScale, pixelsPerMetre);
|
|
1552
|
-
gl.uniform1f(L.uEps, eps);
|
|
1553
|
-
gl.uniform1f(L.uOrthoH, ortho);
|
|
1554
|
-
gl.uniform1i(L.uBoxes, env.counts.boxes);
|
|
1555
|
-
gl.uniform1i(L.uWedges, env.counts.wedges);
|
|
1556
|
-
gl.uniform1i(L.uCaps, env.counts.capsules);
|
|
1557
|
-
for (const blk of env.blocks) gl.bindBufferBase(gl.UNIFORM_BUFFER, blk.i, blk.buf);
|
|
1558
|
-
pass(hp.p, x.fb, [[env.dataTex, L.uData], [env.depthTex, L.uDepth]]);
|
|
1559
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
1560
|
-
return x.fb;
|
|
1561
|
-
}
|
|
1562
|
-
};
|
|
1563
|
-
return {
|
|
1564
|
-
...api,
|
|
1565
|
-
mode: bakeMode,
|
|
1566
|
-
pass,
|
|
1567
|
-
readTarget,
|
|
1568
|
-
dispose() {
|
|
1569
|
-
if (!extra) return;
|
|
1570
|
-
for (const p of [extra.world, extra.index, extra.depth, extra.height]) if (p) gl.deleteProgram(p.p);
|
|
1571
|
-
gl.deleteTexture(extra.tex);
|
|
1572
|
-
gl.deleteFramebuffer(extra.fb);
|
|
1573
|
-
extra = null;
|
|
1574
|
-
}
|
|
1575
|
-
};
|
|
1576
|
-
}
|
|
1577
|
-
|
|
1578
|
-
// ../../../../../../keel-engine/packages/render/src/world-buffers.ts
|
|
1579
|
-
function uploadWorld(gl, blocks, { boxes = [], wedges = [], capsules = [] }) {
|
|
1580
|
-
const [boxBlock, wedgeBlock, capBlock] = blocks;
|
|
1581
|
-
let nBoxes = 0, nWedges = 0, nCaps = 0;
|
|
1582
|
-
const bx = [];
|
|
1583
|
-
const wd = [...wedges];
|
|
1584
|
-
for (const b of boxes) (b.kind === "wedge" ? wd : bx).push(b);
|
|
1585
|
-
nBoxes = Math.min(MAX_BOXES, bx.length);
|
|
1586
|
-
for (let i = 0; i < nBoxes; i += 1) {
|
|
1587
|
-
const b = bx[i];
|
|
1588
|
-
boxBlock.data.set([b.c[0], b.c[1], b.c[2], b.mat ?? 0, b.h[0], b.h[1], b.h[2], b.yaw ?? 0], i * 8);
|
|
1589
|
-
}
|
|
1590
|
-
nWedges = Math.min(MAX_WEDGES, wd.length);
|
|
1591
|
-
for (let i = 0; i < nWedges; i += 1) {
|
|
1592
|
-
const w = wd[i];
|
|
1593
|
-
wedgeBlock.data.set([w.c[0], w.c[1], w.c[2], w.mat ?? 0, w.h[0], w.h[1], w.h[2], w.yaw ?? 0, Math.max(0, Math.min(0.98, w.lo ?? 0)), Math.max(0, w.skin ?? 0), Math.max(-1, Math.min(1, w.top?.[0] ?? -1)), Math.max(-1, Math.min(1, w.top?.[1] ?? 1))], i * 12);
|
|
1594
|
-
}
|
|
1595
|
-
nCaps = Math.min(MAX_CAPS, capsules.length);
|
|
1596
|
-
for (let i = 0; i < nCaps; i += 1) {
|
|
1597
|
-
const c = capsules[i];
|
|
1598
|
-
capBlock.data.set([c.a[0], c.a[1], c.a[2], c.r, c.b[0], c.b[1], c.b[2], c.mat ?? 0], i * 8);
|
|
1599
|
-
}
|
|
1600
|
-
for (const [blk, n] of [[boxBlock, nBoxes * 8], [wedgeBlock, nWedges * 12], [capBlock, nCaps * 8]]) {
|
|
1601
|
-
gl.bindBuffer(gl.UNIFORM_BUFFER, blk.buf);
|
|
1602
|
-
if (n) gl.bufferSubData(gl.UNIFORM_BUFFER, 0, blk.data, 0, n);
|
|
1603
|
-
}
|
|
1604
|
-
return { boxes: nBoxes, wedges: nWedges, capsules: nCaps, dropped: bx.length - nBoxes + wd.length - nWedges + capsules.length - nCaps };
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
// ../../../../../../keel-engine/packages/render/src/pixel-renderer.ts
|
|
1608
|
-
var norm3 = (a) => {
|
|
1609
|
-
const l = Math.hypot(a[0], a[1], a[2]) || 1;
|
|
1610
|
-
return [a[0] / l, a[1] / l, a[2] / l];
|
|
1611
|
-
};
|
|
1612
|
-
var dot2 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
1613
|
-
function createPixelRenderer(canvas, { width = 128, height = 128, bakeOnly = false, profile: profile2 } = {}) {
|
|
1614
|
-
const ctx = canvas.getContext("webgl2", { antialias: false, preserveDrawingBuffer: true });
|
|
1615
|
-
if (!ctx) throw new Error("WebGL2 is not available");
|
|
1616
|
-
const gl = ctx;
|
|
1617
|
-
let worldP = null, pixelP = null, pointsP = null;
|
|
1618
|
-
let directPixelP = null;
|
|
1619
|
-
const colourPrograms = () => {
|
|
1620
|
-
if (!worldP) {
|
|
1621
|
-
const w = program(gl, FULLSCREEN_VS, WORLD_FS);
|
|
1622
|
-
if (bakeOnly) ["Boxes", "Wedges", "Capsules"].forEach((name, i) => gl.uniformBlockBinding(w.p, gl.getUniformBlockIndex(w.p, name), i));
|
|
1623
|
-
worldP = w;
|
|
1624
|
-
pixelP = program(gl, FULLSCREEN_VS, PIXEL_FS);
|
|
1625
|
-
pointsP = program(gl, POINTS_VS, POINTS_FS);
|
|
1626
|
-
}
|
|
1627
|
-
return { world: worldP, pixel: pixelP, points: pointsP };
|
|
1628
|
-
};
|
|
1629
|
-
if (!bakeOnly) colourPrograms();
|
|
1630
|
-
const quad = gl.createBuffer();
|
|
1631
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, quad);
|
|
1632
|
-
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
|
|
1633
|
-
const pbuf = gl.createBuffer();
|
|
1634
|
-
const nearest = () => {
|
|
1635
|
-
for (const [k, v] of [[gl.TEXTURE_MIN_FILTER, gl.NEAREST], [gl.TEXTURE_MAG_FILTER, gl.NEAREST], [gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE], [gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE]]) gl.texParameteri(gl.TEXTURE_2D, k, v);
|
|
1636
|
-
};
|
|
1637
|
-
const floatTex = (w, h, data = null) => {
|
|
1638
|
-
const t = gl.createTexture();
|
|
1639
|
-
gl.bindTexture(gl.TEXTURE_2D, t);
|
|
1640
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, w, h, 0, gl.RGBA, gl.FLOAT, data);
|
|
1641
|
-
nearest();
|
|
1642
|
-
return t;
|
|
1643
|
-
};
|
|
1644
|
-
let W2 = width;
|
|
1645
|
-
let H = height;
|
|
1646
|
-
let fbo = null;
|
|
1647
|
-
let dataTex = null;
|
|
1648
|
-
let data2Tex = null;
|
|
1649
|
-
let depthTex = null;
|
|
1650
|
-
function target(w, h) {
|
|
1651
|
-
W2 = w;
|
|
1652
|
-
H = h;
|
|
1653
|
-
canvas.width = W2;
|
|
1654
|
-
canvas.height = H;
|
|
1655
|
-
for (const t of [dataTex, data2Tex, depthTex]) if (t) gl.deleteTexture(t);
|
|
1656
|
-
if (fbo) gl.deleteFramebuffer(fbo);
|
|
1657
|
-
const rgba8 = () => {
|
|
1658
|
-
const t = gl.createTexture();
|
|
1659
|
-
gl.bindTexture(gl.TEXTURE_2D, t);
|
|
1660
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, W2, H, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
|
1661
|
-
nearest();
|
|
1662
|
-
return t;
|
|
1663
|
-
};
|
|
1664
|
-
dataTex = rgba8();
|
|
1665
|
-
data2Tex = rgba8();
|
|
1666
|
-
depthTex = gl.createTexture();
|
|
1667
|
-
gl.bindTexture(gl.TEXTURE_2D, depthTex);
|
|
1668
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, W2, H, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
|
|
1669
|
-
nearest();
|
|
1670
|
-
fbo = gl.createFramebuffer();
|
|
1671
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
|
|
1672
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, dataTex, 0);
|
|
1673
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, data2Tex, 0);
|
|
1674
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTex, 0);
|
|
1675
|
-
gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
|
|
1676
|
-
fxDirty = true;
|
|
1677
|
-
}
|
|
1678
|
-
const blocks = [["Boxes", 2 * MAX_BOXES], ["Wedges", 3 * MAX_WEDGES], ["Capsules", 2 * MAX_CAPS]].map(([name, vecs], i) => {
|
|
1679
|
-
const data = new Float32Array(vecs * 4);
|
|
1680
|
-
const buf = gl.createBuffer();
|
|
1681
|
-
gl.bindBuffer(gl.UNIFORM_BUFFER, buf);
|
|
1682
|
-
gl.bufferData(gl.UNIFORM_BUFFER, data.byteLength, gl.DYNAMIC_DRAW);
|
|
1683
|
-
if (worldP) gl.uniformBlockBinding(worldP.p, gl.getUniformBlockIndex(worldP.p, name), i);
|
|
1684
|
-
return { data, buf, i };
|
|
1685
|
-
});
|
|
1686
|
-
const [boxBlock, wedgeBlock, capBlock] = blocks;
|
|
1687
|
-
const rampRows = new Float32Array(MAX_RAMPS * 2 * 4);
|
|
1688
|
-
const rampTex = floatTex(MAX_RAMPS, 2, rampRows);
|
|
1689
|
-
const matRows = new Float32Array(256 * 4);
|
|
1690
|
-
const matTex = floatTex(256, 1, matRows);
|
|
1691
|
-
const screenTex = gl.createTexture();
|
|
1692
|
-
gl.bindTexture(gl.TEXTURE_2D, screenTex);
|
|
1693
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R8, SCREEN_TILE, SCREEN_TILE, 0, gl.RED, gl.UNSIGNED_BYTE, new Uint8Array(SCREEN_TILE * SCREEN_TILE));
|
|
1694
|
-
nearest();
|
|
1695
|
-
let screenLoaded = null;
|
|
1696
|
-
const timer = gl.getExtension("EXT_disjoint_timer_query_webgl2");
|
|
1697
|
-
let query = null;
|
|
1698
|
-
let gpuMs = null;
|
|
1699
|
-
let palTex = null;
|
|
1700
|
-
let palette = [];
|
|
1701
|
-
let rampIndex = /* @__PURE__ */ new Map();
|
|
1702
|
-
let rampList = [];
|
|
1703
|
-
let nBoxes = 0;
|
|
1704
|
-
let nWedges = 0;
|
|
1705
|
-
let nCaps = 0;
|
|
1706
|
-
let style = { screen: 4, dither: 0.9, outline: 1 };
|
|
1707
|
-
let activeProfile = targetProfile("native");
|
|
1708
|
-
let authoredSize = [width, height];
|
|
1709
|
-
let authoredStyle = { ...style };
|
|
1710
|
-
let authoredPalette = [];
|
|
1711
|
-
let authoredRamps = {};
|
|
1712
|
-
let fxList = [];
|
|
1713
|
-
let fxResolved = [];
|
|
1714
|
-
let fxU = null;
|
|
1715
|
-
let fxDirty = true;
|
|
1716
|
-
function uploadRamps() {
|
|
1717
|
-
rampRows.fill(0);
|
|
1718
|
-
rampList.forEach(([base, len2], i) => {
|
|
1719
|
-
rampRows.set([base, len2, 0, 0], i * 4);
|
|
1720
|
-
});
|
|
1721
|
-
for (let i = 0; i < MAX_RAMPS; i += 1) rampRows[(MAX_RAMPS + i) * 4] = -1;
|
|
1722
|
-
if (fxU) {
|
|
1723
|
-
for (const [r, from, speed] of fxU.ramps.cycle) {
|
|
1724
|
-
rampRows[r * 4 + 2] = from;
|
|
1725
|
-
rampRows[r * 4 + 3] = speed;
|
|
1726
|
-
}
|
|
1727
|
-
for (const [r, to] of fxU.ramps.grade) rampRows[(MAX_RAMPS + r) * 4] = to;
|
|
1728
|
-
}
|
|
1729
|
-
gl.bindTexture(gl.TEXTURE_2D, rampTex);
|
|
1730
|
-
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, MAX_RAMPS, 2, gl.RGBA, gl.FLOAT, rampRows);
|
|
1731
|
-
}
|
|
1732
|
-
function refreshFx() {
|
|
1733
|
-
fxResolved = resolveFx(fxList, { width: W2, height: H });
|
|
1734
|
-
const u = fxUniforms(fxResolved, { ramp: (n) => rampIndex.get(n) ?? -1, rampOf: (i) => rampList[i] ?? [0, 1], style, far: FAR });
|
|
1735
|
-
fxU = u;
|
|
1736
|
-
if (u.screen && u.screen !== screenLoaded) {
|
|
1737
|
-
gl.bindTexture(gl.TEXTURE_2D, screenTex);
|
|
1738
|
-
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
1739
|
-
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, SCREEN_TILE, SCREEN_TILE, gl.RED, gl.UNSIGNED_BYTE, screenTile(u.screen, SCREEN_TILE));
|
|
1740
|
-
screenLoaded = u.screen;
|
|
1741
|
-
}
|
|
1742
|
-
uploadRamps();
|
|
1743
|
-
fxDirty = false;
|
|
1744
|
-
return u;
|
|
1745
|
-
}
|
|
1746
|
-
target(W2, H);
|
|
1747
|
-
let edges = "smooth";
|
|
1748
|
-
let supersample = "auto";
|
|
1749
|
-
let lastK = 1;
|
|
1750
|
-
let fine = null;
|
|
1751
|
-
let smoothPixelP = null, smoothDirectP = null, resolveP = null, smoothDepthOutP = null;
|
|
1752
|
-
function fineBuffers(k) {
|
|
1753
|
-
if (fine && fine.k === k && fine.w === W2 * k && fine.h === H * k) return fine;
|
|
1754
|
-
if (fine) {
|
|
1755
|
-
for (const t of [fine.data, fine.data2, fine.depth, fine.colour]) gl.deleteTexture(t);
|
|
1756
|
-
gl.deleteFramebuffer(fine.fbo);
|
|
1757
|
-
gl.deleteFramebuffer(fine.colourFbo);
|
|
1758
|
-
}
|
|
1759
|
-
const w = W2 * k, h = H * k;
|
|
1760
|
-
const tex = (internal, format, type) => {
|
|
1761
|
-
const t = gl.createTexture();
|
|
1762
|
-
gl.bindTexture(gl.TEXTURE_2D, t);
|
|
1763
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, internal, w, h, 0, format, type, null);
|
|
1764
|
-
nearest();
|
|
1765
|
-
return t;
|
|
1766
|
-
};
|
|
1767
|
-
const data = tex(gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE), data2 = tex(gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE), depth = tex(gl.DEPTH_COMPONENT24, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT);
|
|
1768
|
-
const fb = gl.createFramebuffer();
|
|
1769
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
|
|
1770
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, data, 0);
|
|
1771
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, data2, 0);
|
|
1772
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depth, 0);
|
|
1773
|
-
gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
|
|
1774
|
-
const colour2 = tex(gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE), colourFbo = gl.createFramebuffer();
|
|
1775
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, colourFbo);
|
|
1776
|
-
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colour2, 0);
|
|
1777
|
-
fine = { k, w, h, fbo: fb, data, data2, depth, colourFbo, colour: colour2 };
|
|
1778
|
-
return fine;
|
|
1779
|
-
}
|
|
1780
|
-
let RW = W2, RH = H, rfbo = null;
|
|
1781
|
-
const indexed = createIndexedPass({
|
|
1782
|
-
gl,
|
|
1783
|
-
quad,
|
|
1784
|
-
matTex,
|
|
1785
|
-
blocks,
|
|
1786
|
-
get width() {
|
|
1787
|
-
return W2;
|
|
1788
|
-
},
|
|
1789
|
-
get height() {
|
|
1790
|
-
return H;
|
|
1791
|
-
},
|
|
1792
|
-
get fbo() {
|
|
1793
|
-
return fbo;
|
|
1794
|
-
},
|
|
1795
|
-
get dataTex() {
|
|
1796
|
-
return dataTex;
|
|
1797
|
-
},
|
|
1798
|
-
get data2Tex() {
|
|
1799
|
-
return data2Tex;
|
|
1800
|
-
},
|
|
1801
|
-
get depthTex() {
|
|
1802
|
-
return depthTex;
|
|
1803
|
-
},
|
|
1804
|
-
counts: { get boxes() {
|
|
1805
|
-
return nBoxes;
|
|
1806
|
-
}, get wedges() {
|
|
1807
|
-
return nWedges;
|
|
1808
|
-
}, get capsules() {
|
|
1809
|
-
return nCaps;
|
|
1810
|
-
} }
|
|
1811
|
-
}, () => program(gl, FULLSCREEN_VS, DEPTH_FS));
|
|
1812
|
-
const { mode: bakeMode, pass, readTarget } = indexed;
|
|
1813
|
-
let rasterState = null;
|
|
1814
|
-
const rasterStats = { meshes: 0, triangles: 0, instances: 0, meshBytes: 0 };
|
|
1815
|
-
function rasterMode() {
|
|
1816
|
-
if (rasterState) return rasterState;
|
|
1817
|
-
const prog = program(gl, RASTER_VS, RASTER_FS);
|
|
1818
|
-
const depthOut = program(gl, FULLSCREEN_VS, DEPTH_OUT_FS);
|
|
1819
|
-
const kind = (t) => {
|
|
1820
|
-
const vao = gl.createVertexArray();
|
|
1821
|
-
gl.bindVertexArray(vao);
|
|
1822
|
-
const vb = gl.createBuffer();
|
|
1823
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, vb);
|
|
1824
|
-
gl.bufferData(gl.ARRAY_BUFFER, t.verts, gl.STATIC_DRAW);
|
|
1825
|
-
gl.enableVertexAttribArray(0);
|
|
1826
|
-
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
|
|
1827
|
-
const nb = gl.createBuffer();
|
|
1828
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, nb);
|
|
1829
|
-
gl.bufferData(gl.ARRAY_BUFFER, t.norms, gl.STATIC_DRAW);
|
|
1830
|
-
gl.enableVertexAttribArray(1);
|
|
1831
|
-
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0);
|
|
1832
|
-
const ib = gl.createBuffer();
|
|
1833
|
-
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ib);
|
|
1834
|
-
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, t.index, gl.STATIC_DRAW);
|
|
1835
|
-
const inst = gl.createBuffer();
|
|
1836
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, inst);
|
|
1837
|
-
gl.bufferData(gl.ARRAY_BUFFER, 1024 * RASTER_FLOATS * 4, gl.DYNAMIC_DRAW);
|
|
1838
|
-
for (let k = 0; k < 4; k += 1) {
|
|
1839
|
-
gl.enableVertexAttribArray(2 + k);
|
|
1840
|
-
gl.vertexAttribPointer(2 + k, 4, gl.FLOAT, false, RASTER_FLOATS * 4, k * 16);
|
|
1841
|
-
gl.vertexAttribDivisor(2 + k, 1);
|
|
1842
|
-
}
|
|
1843
|
-
gl.bindVertexArray(null);
|
|
1844
|
-
return { vao, inst, cap: 1024, count: t.index.length };
|
|
1845
|
-
};
|
|
1846
|
-
const box2 = kind(boxTemplate());
|
|
1847
|
-
rasterState = { prog, depthOut, box: box2, wedge: kind(boxTemplate()), capsule: kind(capsuleTemplate(8, 2)), meshes: /* @__PURE__ */ new Map() };
|
|
1848
|
-
return rasterState;
|
|
1849
|
-
}
|
|
1850
|
-
function drawRaster(frame, eye, fwd, right, up, tanF, sunN, waterY, fogNear, fogFar, time) {
|
|
1851
|
-
const R = rasterMode();
|
|
1852
|
-
const RU = R.prog.loc;
|
|
1853
|
-
gl.enable(gl.DEPTH_TEST);
|
|
1854
|
-
gl.depthFunc(gl.LESS);
|
|
1855
|
-
gl.depthMask(true);
|
|
1856
|
-
gl.useProgram(R.prog.p);
|
|
1857
|
-
gl.uniform3fv(RU.uEye, eye);
|
|
1858
|
-
gl.uniform3fv(RU.uFwd, fwd);
|
|
1859
|
-
gl.uniform3fv(RU.uRight, right);
|
|
1860
|
-
gl.uniform3fv(RU.uUp, up);
|
|
1861
|
-
gl.uniform1f(RU.uTan, tanF);
|
|
1862
|
-
gl.uniform1f(RU.uAspect, RW / RH);
|
|
1863
|
-
gl.activeTexture(gl.TEXTURE1);
|
|
1864
|
-
gl.bindTexture(gl.TEXTURE_2D, matTex);
|
|
1865
|
-
gl.uniform1i(RU.uMats, 1);
|
|
1866
|
-
gl.uniform3fv(RU.uSun, sunN);
|
|
1867
|
-
gl.uniform1f(RU.uWaterY, frame.waterY ?? waterY);
|
|
1868
|
-
gl.uniform1f(RU.uFogNear, fogNear);
|
|
1869
|
-
gl.uniform1f(RU.uFogFar, fogFar);
|
|
1870
|
-
gl.uniform2f(RU.uRes, RW, RH);
|
|
1871
|
-
gl.uniform1f(RU.uTime, time);
|
|
1872
|
-
let tris = 0, insts = 0, meshes = 0;
|
|
1873
|
-
for (const key2 of frame.meshes ?? []) {
|
|
1874
|
-
const m = R.meshes.get(key2);
|
|
1875
|
-
if (!m || !m.count) continue;
|
|
1876
|
-
gl.uniform1i(RU.uKind, 0);
|
|
1877
|
-
gl.bindVertexArray(m.vao);
|
|
1878
|
-
if (m.indexed) gl.drawElements(gl.TRIANGLES, m.count, gl.UNSIGNED_INT, 0);
|
|
1879
|
-
else gl.drawArrays(gl.TRIANGLES, 0, m.count);
|
|
1880
|
-
tris += m.count / 3;
|
|
1881
|
-
meshes += 1;
|
|
1882
|
-
}
|
|
1883
|
-
const solids = frame.solids;
|
|
1884
|
-
if (solids) {
|
|
1885
|
-
for (const [k, buf, id] of [[R.box, solids.boxes, 1], [R.wedge, solids.wedges, 2], [R.capsule, solids.capsules, 3]]) {
|
|
1886
|
-
if (!buf.count) continue;
|
|
1887
|
-
gl.bindVertexArray(k.vao);
|
|
1888
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, k.inst);
|
|
1889
|
-
if (buf.count > k.cap) {
|
|
1890
|
-
k.cap = Math.max(buf.count, k.cap * 2);
|
|
1891
|
-
gl.bufferData(gl.ARRAY_BUFFER, k.cap * RASTER_FLOATS * 4, gl.DYNAMIC_DRAW);
|
|
1892
|
-
}
|
|
1893
|
-
gl.bufferSubData(gl.ARRAY_BUFFER, 0, buf.data, 0, buf.count * RASTER_FLOATS);
|
|
1894
|
-
gl.uniform1i(RU.uKind, id);
|
|
1895
|
-
gl.drawElementsInstanced(gl.TRIANGLES, k.count, gl.UNSIGNED_SHORT, 0, buf.count);
|
|
1896
|
-
tris += k.count / 3 * buf.count;
|
|
1897
|
-
insts += buf.count;
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
gl.bindVertexArray(null);
|
|
1901
|
-
rasterStats.meshes = meshes;
|
|
1902
|
-
rasterStats.triangles = tris;
|
|
1903
|
-
rasterStats.instances = insts;
|
|
1904
|
-
if (frame.draw) {
|
|
1905
|
-
frame.draw({ gl, eye: [eye[0], eye[1], eye[2]], forward: fwd, right, up, tan: tanF, width: RW, height: RH, far: FAR, time, sun: sunN });
|
|
1906
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, rfbo);
|
|
1907
|
-
gl.viewport(0, 0, RW, RH);
|
|
1908
|
-
gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
|
|
1909
|
-
gl.enable(gl.DEPTH_TEST);
|
|
1910
|
-
gl.depthFunc(gl.LESS);
|
|
1911
|
-
gl.depthMask(true);
|
|
1912
|
-
}
|
|
1913
|
-
}
|
|
1914
|
-
function writeDepthOut(map) {
|
|
1915
|
-
const R = rasterMode();
|
|
1916
|
-
const scale2 = map === true ? 1 : map.scale ?? 1, bias = map === true ? 0 : map.bias ?? 0;
|
|
1917
|
-
gl.enable(gl.DEPTH_TEST);
|
|
1918
|
-
gl.depthFunc(gl.ALWAYS);
|
|
1919
|
-
gl.depthMask(true);
|
|
1920
|
-
gl.colorMask(false, false, false, false);
|
|
1921
|
-
const D = lastK > 1 ? smoothDepthOutP ??= program(gl, FULLSCREEN_VS, smoothDepthOutFs(DEPTH_OUT_FS)) : R.depthOut;
|
|
1922
|
-
gl.useProgram(D.p);
|
|
1923
|
-
gl.uniform2f(D.loc.uMap, scale2, bias);
|
|
1924
|
-
if (lastK > 1) gl.uniform1i(D.loc.uSS, lastK);
|
|
1925
|
-
pass(D.p, null, [[lastK > 1 ? fine.depth : depthTex, D.loc.uDepth]]);
|
|
1926
|
-
gl.colorMask(true, true, true, true);
|
|
1927
|
-
gl.disable(gl.DEPTH_TEST);
|
|
1928
|
-
}
|
|
1929
|
-
const api = {
|
|
1930
|
-
gl,
|
|
1931
|
-
get width() {
|
|
1932
|
-
return W2;
|
|
1933
|
-
},
|
|
1934
|
-
get height() {
|
|
1935
|
-
return H;
|
|
1936
|
-
},
|
|
1937
|
-
limits: {
|
|
1938
|
-
boxes: MAX_BOXES,
|
|
1939
|
-
wedges: MAX_WEDGES,
|
|
1940
|
-
capsules: MAX_CAPS,
|
|
1941
|
-
ramps: MAX_RAMPS,
|
|
1942
|
-
materials: MAX_MATERIALS,
|
|
1943
|
-
colours: MAX_COLOURS,
|
|
1944
|
-
fragmentUniformVectors: gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS),
|
|
1945
|
-
maxTexture: gl.getParameter(gl.MAX_TEXTURE_SIZE)
|
|
1946
|
-
},
|
|
1947
|
-
setTarget(w, h) {
|
|
1948
|
-
if (activeProfile.kind === "hardware" && (w !== activeProfile.width || h !== activeProfile.height)) throw new RangeError(`${activeProfile.label} preview requires ${activeProfile.width} \xD7 ${activeProfile.height}`);
|
|
1949
|
-
target(Math.max(8, w | 0), Math.max(8, h | 0));
|
|
1950
|
-
if (activeProfile.kind === "native") authoredSize = [W2, H];
|
|
1951
|
-
},
|
|
1952
|
-
get profile() {
|
|
1953
|
-
return activeProfile;
|
|
1954
|
-
},
|
|
1955
|
-
setProfile(input) {
|
|
1956
|
-
const next = targetProfile(input);
|
|
1957
|
-
activeProfile = next;
|
|
1958
|
-
if (next.kind === "native") {
|
|
1959
|
-
target(authoredSize[0], authoredSize[1]);
|
|
1960
|
-
style = { ...authoredStyle };
|
|
1961
|
-
} else {
|
|
1962
|
-
target(next.width, next.height);
|
|
1963
|
-
style = { ...authoredStyle, screen: next.screen, dither: next.dither };
|
|
1964
|
-
}
|
|
1965
|
-
if (authoredPalette.length) api.setPalette(authoredPalette, authoredRamps);
|
|
1966
|
-
fxDirty = true;
|
|
1967
|
-
},
|
|
1968
|
-
setPalette(colours, ramps) {
|
|
1969
|
-
if (colours.length > MAX_COLOURS) throw new RangeError(`${colours.length} colours: at most ${MAX_COLOURS}`);
|
|
1970
|
-
const sourceColours = colours.map((c) => [c[0] ?? 0, c[1] ?? 0, c[2] ?? 0]);
|
|
1971
|
-
if (activeProfile.kind !== "native") colours = adaptTargetPalette(sourceColours, activeProfile.id);
|
|
1972
|
-
palette = colours.map((c) => [c[0] ?? 0, c[1] ?? 0, c[2] ?? 0]);
|
|
1973
|
-
const rows = Math.max(1, Math.ceil(colours.length / PALETTE_WIDTH));
|
|
1974
|
-
const bytes = new Uint8Array(PALETTE_WIDTH * rows * 4);
|
|
1975
|
-
colours.forEach((c, i) => {
|
|
1976
|
-
bytes[i * 4] = c[0] ?? 0;
|
|
1977
|
-
bytes[i * 4 + 1] = c[1] ?? 0;
|
|
1978
|
-
bytes[i * 4 + 2] = c[2] ?? 0;
|
|
1979
|
-
bytes[i * 4 + 3] = 255;
|
|
1980
|
-
});
|
|
1981
|
-
if (palTex) gl.deleteTexture(palTex);
|
|
1982
|
-
palTex = gl.createTexture();
|
|
1983
|
-
gl.bindTexture(gl.TEXTURE_2D, palTex);
|
|
1984
|
-
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4);
|
|
1985
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, PALETTE_WIDTH, rows, 0, gl.RGBA, gl.UNSIGNED_BYTE, bytes);
|
|
1986
|
-
nearest();
|
|
1987
|
-
const entries = Object.entries(ramps);
|
|
1988
|
-
if (entries.length > MAX_RAMPS) throw new RangeError(`${entries.length} ramps: at most ${MAX_RAMPS}`);
|
|
1989
|
-
authoredPalette = sourceColours;
|
|
1990
|
-
authoredRamps = Object.fromEntries(entries.map(([name, slot]) => [name, [...slot]]));
|
|
1991
|
-
rampIndex = /* @__PURE__ */ new Map();
|
|
1992
|
-
rampList = entries.map(([name, [base, len2]], i) => {
|
|
1993
|
-
rampIndex.set(name, i);
|
|
1994
|
-
return [base, len2];
|
|
1995
|
-
});
|
|
1996
|
-
fxDirty = true;
|
|
1997
|
-
},
|
|
1998
|
-
get palette() {
|
|
1999
|
-
return palette;
|
|
2000
|
-
},
|
|
2001
|
-
ramp: (name) => rampIndex.get(name) ?? 0,
|
|
2002
|
-
setMaterials(list) {
|
|
2003
|
-
if (list.length > MAX_MATERIALS) throw new RangeError(`${list.length} materials: at most ${MAX_MATERIALS}`);
|
|
2004
|
-
matRows.fill(0);
|
|
2005
|
-
list.forEach((m, i) => {
|
|
2006
|
-
matRows.set([rampIndex.get(m.ramp) ?? 0, m.light ?? 1, m.pattern ?? 0, m.glow ?? 0], i * 4);
|
|
2007
|
-
});
|
|
2008
|
-
gl.bindTexture(gl.TEXTURE_2D, matTex);
|
|
2009
|
-
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 256, 1, gl.RGBA, gl.FLOAT, matRows);
|
|
2010
|
-
},
|
|
2011
|
-
setStyle({ screen: s = style.screen, dither: d = style.dither, outline: o = style.outline, edges: e = edges, supersample: k = supersample } = {}) {
|
|
2012
|
-
style = { screen: s, dither: d, outline: o ? 1 : 0 };
|
|
2013
|
-
edges = e;
|
|
2014
|
-
supersample = k;
|
|
2015
|
-
authoredStyle = { ...style };
|
|
2016
|
-
fxDirty = true;
|
|
2017
|
-
},
|
|
2018
|
-
setWorld(world) {
|
|
2019
|
-
const counts = uploadWorld(gl, blocks, world);
|
|
2020
|
-
nBoxes = counts.boxes;
|
|
2021
|
-
nWedges = counts.wedges;
|
|
2022
|
-
nCaps = counts.capsules;
|
|
2023
|
-
return counts;
|
|
2024
|
-
},
|
|
2025
|
-
setFx(list = []) {
|
|
2026
|
-
fxList = list.map((e) => ({ ...e }));
|
|
2027
|
-
resolveFx(fxList, { width: W2, height: H });
|
|
2028
|
-
fxDirty = true;
|
|
2029
|
-
},
|
|
2030
|
-
toggleFx(name, on) {
|
|
2031
|
-
fxList = toggleFx(fxList, name, on);
|
|
2032
|
-
fxDirty = true;
|
|
2033
|
-
},
|
|
2034
|
-
get fx() {
|
|
2035
|
-
return fxList.map((e) => ({ ...e }));
|
|
2036
|
-
},
|
|
2037
|
-
get fxResolved() {
|
|
2038
|
-
if (fxDirty) refreshFx();
|
|
2039
|
-
return fxResolved;
|
|
2040
|
-
},
|
|
2041
|
-
allFx: ALL_FX,
|
|
2042
|
-
get gpuMs() {
|
|
2043
|
-
return gpuMs;
|
|
2044
|
-
},
|
|
2045
|
-
get supersample() {
|
|
2046
|
-
return lastK;
|
|
2047
|
-
},
|
|
2048
|
-
render({ eye, target: look, fov = 1.2, time = 0, sun = [0.4, 0.8, 0.3], waterY = 0, fogNear = 25, fogFar = 110, particles = [], raster, depthOut }) {
|
|
2049
|
-
const { world, pixel, points } = colourPrograms();
|
|
2050
|
-
const fx = fxDirty || !fxU ? refreshFx() : fxU;
|
|
2051
|
-
let timing = false;
|
|
2052
|
-
if (timer) {
|
|
2053
|
-
if (query && gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE)) {
|
|
2054
|
-
if (!gl.getParameter(timer.GPU_DISJOINT_EXT)) gpuMs = gl.getQueryParameter(query, gl.QUERY_RESULT) / 1e6;
|
|
2055
|
-
gl.deleteQuery(query);
|
|
2056
|
-
query = null;
|
|
2057
|
-
}
|
|
2058
|
-
if (!query) {
|
|
2059
|
-
query = gl.createQuery();
|
|
2060
|
-
gl.beginQuery(timer.TIME_ELAPSED_EXT, query);
|
|
2061
|
-
timing = true;
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
2064
|
-
const { forward: fwd, right, up } = cameraBasis(eye, look);
|
|
2065
|
-
const tanF = Math.tan(fov / 2);
|
|
2066
|
-
const sunN = norm3(sun);
|
|
2067
|
-
const k = edges === "smooth" && activeProfile.kind === "native" ? supersampleFor(W2, H, supersample) : 1;
|
|
2068
|
-
const F = k > 1 ? fineBuffers(k) : null;
|
|
2069
|
-
lastK = k;
|
|
2070
|
-
RW = F ? F.w : W2;
|
|
2071
|
-
RH = F ? F.h : H;
|
|
2072
|
-
rfbo = F ? F.fbo : fbo;
|
|
2073
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, rfbo);
|
|
2074
|
-
gl.viewport(0, 0, RW, RH);
|
|
2075
|
-
gl.enable(gl.DEPTH_TEST);
|
|
2076
|
-
gl.depthFunc(gl.ALWAYS);
|
|
2077
|
-
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
2078
|
-
const U = world.loc;
|
|
2079
|
-
gl.useProgram(world.p);
|
|
2080
|
-
gl.uniform2f(U.uRes, RW, RH);
|
|
2081
|
-
gl.uniform3fv(U.uEye, eye);
|
|
2082
|
-
gl.uniform3fv(U.uFwd, fwd);
|
|
2083
|
-
gl.uniform3fv(U.uRight, right);
|
|
2084
|
-
gl.uniform3fv(U.uUp, up);
|
|
2085
|
-
gl.uniform1f(U.uTan, tanF);
|
|
2086
|
-
gl.uniform1f(U.uTime, time);
|
|
2087
|
-
gl.uniform1i(U.uBoxes, nBoxes);
|
|
2088
|
-
gl.uniform1i(U.uWedges, nWedges);
|
|
2089
|
-
gl.uniform1i(U.uCaps, nCaps);
|
|
2090
|
-
for (const blk of blocks) gl.bindBufferBase(gl.UNIFORM_BUFFER, blk.i, blk.buf);
|
|
2091
|
-
gl.activeTexture(gl.TEXTURE1);
|
|
2092
|
-
gl.bindTexture(gl.TEXTURE_2D, matTex);
|
|
2093
|
-
gl.uniform1i(U.uMats, 1);
|
|
2094
|
-
gl.uniform3fv(U.uSun, sunN);
|
|
2095
|
-
gl.uniform1f(U.uWaterY, waterY);
|
|
2096
|
-
gl.uniform1f(U.uFogNear, fogNear);
|
|
2097
|
-
gl.uniform1f(U.uFogFar, fogFar);
|
|
2098
|
-
if (raster?.world === false) {
|
|
2099
|
-
gl.clearBufferfv(gl.COLOR, 0, [0.5, (matRows[SKY_MAT * 4] ?? 0) / 255, SKY_MAT / 255, 1]);
|
|
2100
|
-
gl.clearBufferfv(gl.COLOR, 1, [0, 1, 0, 0]);
|
|
2101
|
-
} else {
|
|
2102
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, quad);
|
|
2103
|
-
const aw = gl.getAttribLocation(world.p, "aPos");
|
|
2104
|
-
gl.enableVertexAttribArray(aw);
|
|
2105
|
-
gl.vertexAttribPointer(aw, 2, gl.FLOAT, false, 0, 0);
|
|
2106
|
-
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
2107
|
-
}
|
|
2108
|
-
if (particles.length) {
|
|
2109
|
-
gl.depthFunc(gl.LESS);
|
|
2110
|
-
const data = new Float32Array(particles.length * 7);
|
|
2111
|
-
particles.forEach((q, i) => data.set([q.p[0], q.p[1], q.p[2], q.size ?? 1, q.light ?? 0.8, (q.ramp === void 0 ? void 0 : rampIndex.get(q.ramp)) ?? 0, q.glow ?? 0], i * 7));
|
|
2112
|
-
gl.useProgram(points.p);
|
|
2113
|
-
const P = points.loc;
|
|
2114
|
-
gl.uniform3fv(P.uEye, eye);
|
|
2115
|
-
gl.uniform3fv(P.uFwd, fwd);
|
|
2116
|
-
gl.uniform3fv(P.uRight, right);
|
|
2117
|
-
gl.uniform3fv(P.uUp, up);
|
|
2118
|
-
gl.uniform1f(P.uTan, tanF);
|
|
2119
|
-
gl.uniform1f(P.uAspect, RW / RH);
|
|
2120
|
-
gl.uniform1f(P.uH, RH);
|
|
2121
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, pbuf);
|
|
2122
|
-
gl.bufferData(gl.ARRAY_BUFFER, data, gl.DYNAMIC_DRAW);
|
|
2123
|
-
const a0 = gl.getAttribLocation(points.p, "aPos");
|
|
2124
|
-
const a1 = gl.getAttribLocation(points.p, "aLook");
|
|
2125
|
-
gl.enableVertexAttribArray(a0);
|
|
2126
|
-
gl.vertexAttribPointer(a0, 4, gl.FLOAT, false, 28, 0);
|
|
2127
|
-
gl.enableVertexAttribArray(a1);
|
|
2128
|
-
gl.vertexAttribPointer(a1, 3, gl.FLOAT, false, 28, 16);
|
|
2129
|
-
gl.drawArrays(gl.POINTS, 0, particles.length);
|
|
2130
|
-
gl.disableVertexAttribArray(a1);
|
|
2131
|
-
}
|
|
2132
|
-
if (raster) drawRaster(raster, eye, fwd, right, up, tanF, sunN, waterY, fogNear, fogFar, time);
|
|
2133
|
-
gl.disable(gl.DEPTH_TEST);
|
|
2134
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, F ? F.colourFbo : null);
|
|
2135
|
-
gl.viewport(0, 0, RW, RH);
|
|
2136
|
-
const pass2 = F ? raster?.draw ? smoothDirectP ??= program(gl, FULLSCREEN_VS, smoothPixelFs(DIRECT_PIXEL_FS)) : smoothPixelP ??= program(gl, FULLSCREEN_VS, smoothPixelFs(PIXEL_FS)) : raster?.draw ? directPixelP ??= program(gl, FULLSCREEN_VS, DIRECT_PIXEL_FS) : pixel;
|
|
2137
|
-
gl.useProgram(pass2.p);
|
|
2138
|
-
const X = pass2.loc;
|
|
2139
|
-
const T = [[F ? F.data : dataTex, "uData"], [F ? F.data2 : data2Tex, "uData2"], [F ? F.depth : depthTex, "uDepth"], [palTex, "uPalette"], [rampTex, "uRamps"], [screenTex, "uScreenTex"]];
|
|
2140
|
-
T.forEach(([t, name], i) => {
|
|
2141
|
-
gl.activeTexture(gl.TEXTURE0 + i);
|
|
2142
|
-
gl.bindTexture(gl.TEXTURE_2D, t);
|
|
2143
|
-
gl.uniform1i(X[name], i);
|
|
2144
|
-
});
|
|
2145
|
-
const u = fx.u;
|
|
2146
|
-
gl.uniform1i(X.uScreen, u.uScreen);
|
|
2147
|
-
gl.uniform1f(X.uDither, u.uDither);
|
|
2148
|
-
gl.uniform1f(X.uTime, time);
|
|
2149
|
-
gl.uniform3fv(X.uOutline, u.uOutline);
|
|
2150
|
-
gl.uniform1i(X.uOutlineInk, u.uOutlineInk);
|
|
2151
|
-
gl.uniform4fv(X.uFog, u.uFog);
|
|
2152
|
-
gl.uniform2fv(X.uFogLook, u.uFogLook);
|
|
2153
|
-
gl.uniform4fv(X.uGlow, u.uGlow);
|
|
2154
|
-
gl.uniform2fv(X.uGlowK, u.uGlowK);
|
|
2155
|
-
gl.uniform4fv(X.uVig, u.uVig);
|
|
2156
|
-
gl.uniform4fv(X.uScan, u.uScan);
|
|
2157
|
-
gl.uniform4fv(X.uCrt, u.uCrt);
|
|
2158
|
-
gl.uniform4fv(X.uRim, u.uRim);
|
|
2159
|
-
let rd = u.uRimDir;
|
|
2160
|
-
if (!rd) {
|
|
2161
|
-
const sx = dot2(sunN, right);
|
|
2162
|
-
const sy = dot2(sunN, up);
|
|
2163
|
-
const l = Math.hypot(sx, sy);
|
|
2164
|
-
rd = l > 1e-3 ? [sx / l, sy / l] : [0, 1];
|
|
2165
|
-
}
|
|
2166
|
-
gl.uniform2fv(X.uRimDir, rd);
|
|
2167
|
-
gl.uniform4fv(X.uFlash, u.uFlash);
|
|
2168
|
-
gl.uniform4iv(X.uFlashMats, u.uFlashMats);
|
|
2169
|
-
gl.uniform2fv(X.uGrade, u.uGrade);
|
|
2170
|
-
gl.uniform1i(X.uCycle, u.uCycle);
|
|
2171
|
-
if (F) gl.uniform1i(X.uSS, k);
|
|
2172
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, quad);
|
|
2173
|
-
const ap = gl.getAttribLocation(pass2.p, "aPos");
|
|
2174
|
-
gl.enableVertexAttribArray(ap);
|
|
2175
|
-
gl.vertexAttribPointer(ap, 2, gl.FLOAT, false, 0, 0);
|
|
2176
|
-
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
2177
|
-
if (F) {
|
|
2178
|
-
const rp = resolveP ??= program(gl, FULLSCREEN_VS, RESOLVE_FS);
|
|
2179
|
-
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
2180
|
-
gl.viewport(0, 0, W2, H);
|
|
2181
|
-
gl.useProgram(rp.p);
|
|
2182
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
2183
|
-
gl.bindTexture(gl.TEXTURE_2D, F.colour);
|
|
2184
|
-
gl.uniform1i(rp.loc.uColour, 0);
|
|
2185
|
-
gl.uniform1i(rp.loc.uSS, k);
|
|
2186
|
-
const ar = gl.getAttribLocation(rp.p, "aPos");
|
|
2187
|
-
gl.enableVertexAttribArray(ar);
|
|
2188
|
-
gl.vertexAttribPointer(ar, 2, gl.FLOAT, false, 0, 0);
|
|
2189
|
-
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
2190
|
-
}
|
|
2191
|
-
if (depthOut) writeDepthOut(depthOut);
|
|
2192
|
-
if (timing && timer) gl.endQuery(timer.TIME_ELAPSED_EXT);
|
|
2193
|
-
},
|
|
2194
|
-
read() {
|
|
2195
|
-
const out = new Uint8Array(W2 * H * 4);
|
|
2196
|
-
gl.readPixels(0, 0, W2, H, gl.RGBA, gl.UNSIGNED_BYTE, out);
|
|
2197
|
-
return out;
|
|
2198
|
-
},
|
|
2199
|
-
readData() {
|
|
2200
|
-
const x = bakeMode();
|
|
2201
|
-
const data = readTarget(fbo, gl.COLOR_ATTACHMENT0);
|
|
2202
|
-
const data2 = readTarget(fbo, gl.COLOR_ATTACHMENT1);
|
|
2203
|
-
gl.disable(gl.DEPTH_TEST);
|
|
2204
|
-
pass(x.depth.p, x.fb, [[depthTex, x.depth.loc.uDepth]]);
|
|
2205
|
-
const packed = readTarget(x.fb, gl.COLOR_ATTACHMENT0);
|
|
2206
|
-
const depth = new Float32Array(W2 * H);
|
|
2207
|
-
for (let i = 0; i < depth.length; i += 1) depth[i] = unpackDepth(packed[i * 4], packed[i * 4 + 1], packed[i * 4 + 2]);
|
|
2208
|
-
return { width: W2, height: H, data, data2, depth };
|
|
2209
|
-
},
|
|
2210
|
-
renderIndexed: indexed.renderIndexed,
|
|
2211
|
-
readIndexed: indexed.readIndexed,
|
|
2212
|
-
renderIndexedHeights: indexed.renderIndexedHeights,
|
|
2213
|
-
setMesh(key2, mesh) {
|
|
2214
|
-
const R = rasterMode();
|
|
2215
|
-
const old = R.meshes.get(key2);
|
|
2216
|
-
if (old) {
|
|
2217
|
-
for (const b of old.bufs) gl.deleteBuffer(b);
|
|
2218
|
-
gl.deleteVertexArray(old.vao);
|
|
2219
|
-
R.meshes.delete(key2);
|
|
2220
|
-
rasterStats.meshBytes -= old.bytes;
|
|
2221
|
-
}
|
|
2222
|
-
if (!mesh) return;
|
|
2223
|
-
const vao = gl.createVertexArray();
|
|
2224
|
-
gl.bindVertexArray(vao);
|
|
2225
|
-
const bufs = [];
|
|
2226
|
-
const attr = (loc, data, size) => {
|
|
2227
|
-
const b = gl.createBuffer();
|
|
2228
|
-
bufs.push(b);
|
|
2229
|
-
gl.bindBuffer(gl.ARRAY_BUFFER, b);
|
|
2230
|
-
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
|
|
2231
|
-
gl.enableVertexAttribArray(loc);
|
|
2232
|
-
gl.vertexAttribPointer(loc, size, gl.FLOAT, false, 0, 0);
|
|
2233
|
-
};
|
|
2234
|
-
attr(0, mesh.positions, 3);
|
|
2235
|
-
attr(1, mesh.normals, 3);
|
|
2236
|
-
attr(2, mesh.looks, 4);
|
|
2237
|
-
for (let k = 3; k < 6; k += 1) {
|
|
2238
|
-
gl.disableVertexAttribArray(k);
|
|
2239
|
-
gl.vertexAttrib4f(k, 0, 0, 0, 0);
|
|
2240
|
-
}
|
|
2241
|
-
let bytes = mesh.positions.byteLength + mesh.normals.byteLength + mesh.looks.byteLength;
|
|
2242
|
-
if (mesh.indices) {
|
|
2243
|
-
const b = gl.createBuffer();
|
|
2244
|
-
bufs.push(b);
|
|
2245
|
-
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, b);
|
|
2246
|
-
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, mesh.indices, gl.STATIC_DRAW);
|
|
2247
|
-
bytes += mesh.indices.byteLength;
|
|
2248
|
-
}
|
|
2249
|
-
gl.bindVertexArray(null);
|
|
2250
|
-
R.meshes.set(key2, { vao, bufs, count: mesh.indices ? mesh.indices.length : mesh.positions.length / 3, indexed: !!mesh.indices, bytes });
|
|
2251
|
-
rasterStats.meshBytes += bytes;
|
|
2252
|
-
},
|
|
2253
|
-
get rasterStats() {
|
|
2254
|
-
return { ...rasterStats };
|
|
2255
|
-
},
|
|
2256
|
-
offPalette(pixels = api.read()) {
|
|
2257
|
-
const set = new Set(palette.map((c) => c[0] << 16 | c[1] << 8 | c[2]));
|
|
2258
|
-
let off = 0;
|
|
2259
|
-
for (let i = 0; i < pixels.length; i += 4) if (!set.has((pixels[i] ?? 0) << 16 | (pixels[i + 1] ?? 0) << 8 | (pixels[i + 2] ?? 0))) off += 1;
|
|
2260
|
-
return off;
|
|
2261
|
-
}
|
|
2262
|
-
};
|
|
2263
|
-
if (profile2 !== void 0) api.setProfile(profile2);
|
|
2264
|
-
return api;
|
|
2265
|
-
}
|
|
2266
|
-
export {
|
|
2267
|
-
createPixelRenderer
|
|
2268
|
-
};
|
|
1
|
+
export { createPixelRenderer } from './keel-render/renderer-api.mjs';
|