@flare-engine/render 0.2.1 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -21
- package/NOTICE +2 -0
- package/README.md +19 -8
- package/dist/index.d.mts +564 -503
- package/dist/index.d.mts.map +1 -0
- package/dist/index.d.ts +564 -503
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1538 -1491
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1498 -1427
- package/dist/index.mjs.map +1 -1
- package/package.json +17 -12
package/dist/index.js
CHANGED
|
@@ -1,695 +1,325 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
|
|
20
|
-
// src/index.ts
|
|
21
|
-
var index_exports = {};
|
|
22
|
-
__export(index_exports, {
|
|
23
|
-
CAVE_INTERIOR_SKSL: () => CAVE_INTERIOR_SKSL,
|
|
24
|
-
Camera2D: () => Camera2D,
|
|
25
|
-
FIRE_SKSL: () => FIRE_SKSL,
|
|
26
|
-
FOG_SKSL: () => FOG_SKSL,
|
|
27
|
-
FOREST_CANOPY_SKSL: () => FOREST_CANOPY_SKSL,
|
|
28
|
-
MOON_CRATER_SKSL: () => MOON_CRATER_SKSL,
|
|
29
|
-
MOUNTAIN_HILLS_SKSL: () => MOUNTAIN_HILLS_SKSL,
|
|
30
|
-
RIVER_WAVE_SKSL: () => RIVER_WAVE_SKSL,
|
|
31
|
-
ROOFS_CITY_SKSL: () => ROOFS_CITY_SKSL,
|
|
32
|
-
Renderer: () => Renderer,
|
|
33
|
-
Sprite: () => Sprite,
|
|
34
|
-
SpriteBatch: () => SpriteBatch,
|
|
35
|
-
WATER_SKSL: () => WATER_SKSL,
|
|
36
|
-
caveInterior: () => caveInterior,
|
|
37
|
-
createFrameRenderer: () => createFrameRenderer,
|
|
38
|
-
createRenderSystem: () => createRenderSystem,
|
|
39
|
-
custom: () => custom,
|
|
40
|
-
drawOrderCompare: () => drawOrderCompare,
|
|
41
|
-
drawText: () => drawText,
|
|
42
|
-
dropShadow: () => dropShadow,
|
|
43
|
-
fire: () => fire,
|
|
44
|
-
flash: () => flash,
|
|
45
|
-
flashIntensity: () => flashIntensity,
|
|
46
|
-
fog: () => fog,
|
|
47
|
-
forestCanopy: () => forestCanopy,
|
|
48
|
-
getKerning: () => getKerning,
|
|
49
|
-
grayscale: () => grayscale,
|
|
50
|
-
measureText: () => measureText,
|
|
51
|
-
moonCrater: () => moonCrater,
|
|
52
|
-
mountainHills: () => mountainHills,
|
|
53
|
-
outline: () => outline,
|
|
54
|
-
parseBMFontJSON: () => parseBMFontJSON,
|
|
55
|
-
riverWave: () => riverWave,
|
|
56
|
-
roofsCity: () => roofsCity,
|
|
57
|
-
sortByDrawOrder: () => sortByDrawOrder,
|
|
58
|
-
tint: () => tint,
|
|
59
|
-
updateEffect: () => updateEffect,
|
|
60
|
-
water: () => water
|
|
61
|
-
});
|
|
62
|
-
module.exports = __toCommonJS(index_exports);
|
|
63
|
-
|
|
64
|
-
// src/draw-order.ts
|
|
65
|
-
function sortByDrawOrder(items, length) {
|
|
66
|
-
const n = length ?? items.length;
|
|
67
|
-
for (let i = 1; i < n; i++) {
|
|
68
|
-
const item = items[i];
|
|
69
|
-
let j = i - 1;
|
|
70
|
-
while (j >= 0 && items[j].z > item.z) {
|
|
71
|
-
items[j + 1] = items[j];
|
|
72
|
-
j--;
|
|
73
|
-
}
|
|
74
|
-
items[j + 1] = item;
|
|
75
|
-
}
|
|
76
|
-
return items;
|
|
77
|
-
}
|
|
78
|
-
function drawOrderCompare(a, b) {
|
|
79
|
-
return a.z - b.z;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// src/camera2d.ts
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _flare_engine_ecs = require("@flare-engine/ecs");
|
|
3
|
+
//#region src/camera2d.ts
|
|
4
|
+
/** Simple pseudo-random for deterministic shake. Avoids Math.random() allocs. */
|
|
83
5
|
function pseudoRandom(seed) {
|
|
84
|
-
|
|
85
|
-
|
|
6
|
+
const x = Math.sin(seed * 12.9898 + seed * 78.233) * 43758.5453;
|
|
7
|
+
return x - Math.floor(x);
|
|
86
8
|
}
|
|
87
|
-
|
|
9
|
+
const PERM = /* @__PURE__ */ new Uint8Array(256);
|
|
88
10
|
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
11
|
+
for (let i = 0; i < 256; i++) PERM[i] = i;
|
|
12
|
+
let s = 2654435769;
|
|
13
|
+
for (let i = 255; i > 0; i--) {
|
|
14
|
+
s = (s ^ s >>> 15) >>> 0;
|
|
15
|
+
s = Math.imul(s, 2246822507) >>> 0;
|
|
16
|
+
s = (s ^ s >>> 13) >>> 0;
|
|
17
|
+
const j = s % (i + 1);
|
|
18
|
+
const t = PERM[i];
|
|
19
|
+
PERM[i] = PERM[j];
|
|
20
|
+
PERM[j] = t;
|
|
21
|
+
}
|
|
100
22
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
23
|
+
const S = Math.SQRT1_2;
|
|
24
|
+
const GRADS = [
|
|
25
|
+
[1, 0],
|
|
26
|
+
[S, S],
|
|
27
|
+
[0, 1],
|
|
28
|
+
[-S, S],
|
|
29
|
+
[-1, 0],
|
|
30
|
+
[-S, -S],
|
|
31
|
+
[0, -1],
|
|
32
|
+
[S, -S]
|
|
111
33
|
];
|
|
112
34
|
function grad2(hash, x, y) {
|
|
113
|
-
|
|
114
|
-
|
|
35
|
+
const g = GRADS[hash & 7];
|
|
36
|
+
return g[0] * x + g[1] * y;
|
|
115
37
|
}
|
|
116
38
|
function fade(t) {
|
|
117
|
-
|
|
39
|
+
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
118
40
|
}
|
|
41
|
+
/** 2D value-gradient noise in [-1, 1]. */
|
|
119
42
|
function noise2d(x, y) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return lerpX0 + v * (lerpX1 - lerpX0);
|
|
43
|
+
const xi = Math.floor(x) & 255;
|
|
44
|
+
const yi = Math.floor(y) & 255;
|
|
45
|
+
const xf = x - Math.floor(x);
|
|
46
|
+
const yf = y - Math.floor(y);
|
|
47
|
+
const u = fade(xf);
|
|
48
|
+
const v = fade(yf);
|
|
49
|
+
const aa = PERM[PERM[xi] + yi];
|
|
50
|
+
const ab = PERM[PERM[xi] + yi + 1];
|
|
51
|
+
const ba = PERM[PERM[xi + 1 & 255] + yi];
|
|
52
|
+
const bb = PERM[PERM[xi + 1 & 255] + yi + 1];
|
|
53
|
+
const x1 = xf;
|
|
54
|
+
const y1 = yf;
|
|
55
|
+
const x2 = xf - 1;
|
|
56
|
+
const y2 = yf - 1;
|
|
57
|
+
const n00 = grad2(aa, x1, y1);
|
|
58
|
+
const n10 = grad2(ba, x2, y1);
|
|
59
|
+
const n01 = grad2(ab, x1, y2);
|
|
60
|
+
const n11 = grad2(bb, x2, y2);
|
|
61
|
+
const lerpX0 = n00 + u * (n10 - n00);
|
|
62
|
+
return lerpX0 + v * (n01 + u * (n11 - n01) - lerpX0);
|
|
141
63
|
}
|
|
64
|
+
/** 2-octave FBM for shake displacement — result in [-1, 1]. */
|
|
142
65
|
function fbmNoise2d(x, y) {
|
|
143
|
-
|
|
66
|
+
return .6667 * noise2d(x, y) + .3333 * noise2d(x * 2, y * 2);
|
|
144
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Camera2D — 2D camera with position, zoom, rotation, follow, and shake.
|
|
70
|
+
* Pure data + methods — no Skia dependency. The renderer reads camera state
|
|
71
|
+
* to build the view transform.
|
|
72
|
+
*/
|
|
145
73
|
var Camera2D = class {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
} else {
|
|
359
|
-
this.y = b.y + b.height / 2;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
// src/renderer.ts
|
|
365
|
-
var Renderer = class {
|
|
366
|
-
/** Current frame stats. */
|
|
367
|
-
stats = { drawCalls: 0, batchCount: 0 };
|
|
368
|
-
_canvas = null;
|
|
369
|
-
_inFrame = false;
|
|
370
|
-
/** Set the canvas to render to. Called once when canvas is available. */
|
|
371
|
-
setCanvas(canvas) {
|
|
372
|
-
this._canvas = canvas;
|
|
373
|
-
}
|
|
374
|
-
/** Get the current canvas. Throws if not in a frame. */
|
|
375
|
-
get canvas() {
|
|
376
|
-
if (!this._canvas) {
|
|
377
|
-
throw new Error("Renderer: no canvas set");
|
|
378
|
-
}
|
|
379
|
-
return this._canvas;
|
|
380
|
-
}
|
|
381
|
-
/** Whether we are currently between begin() and end(). */
|
|
382
|
-
get inFrame() {
|
|
383
|
-
return this._inFrame;
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Begin a new frame. Clears the canvas and resets stats.
|
|
387
|
-
* @param clearColor 32-bit ARGB color to clear with (default black = 0xFF000000).
|
|
388
|
-
*/
|
|
389
|
-
begin(clearColor = 4278190080) {
|
|
390
|
-
if (this._inFrame) {
|
|
391
|
-
throw new Error("Renderer: begin() called while already in frame");
|
|
392
|
-
}
|
|
393
|
-
this._inFrame = true;
|
|
394
|
-
this.stats.drawCalls = 0;
|
|
395
|
-
this.stats.batchCount = 0;
|
|
396
|
-
this.canvas.save();
|
|
397
|
-
this.canvas.clear(clearColor);
|
|
398
|
-
}
|
|
399
|
-
/**
|
|
400
|
-
* End the current frame. Restores canvas state.
|
|
401
|
-
*/
|
|
402
|
-
end() {
|
|
403
|
-
if (!this._inFrame) {
|
|
404
|
-
throw new Error("Renderer: end() called without begin()");
|
|
405
|
-
}
|
|
406
|
-
this.canvas.restore();
|
|
407
|
-
this._inFrame = false;
|
|
408
|
-
}
|
|
409
|
-
/**
|
|
410
|
-
* Apply camera transform to the canvas.
|
|
411
|
-
* Call after begin() and before drawing.
|
|
412
|
-
*/
|
|
413
|
-
applyCamera(cameraX, cameraY, zoom, rotation, viewportWidth, viewportHeight) {
|
|
414
|
-
const c = this.canvas;
|
|
415
|
-
c.translate(viewportWidth / 2, viewportHeight / 2);
|
|
416
|
-
c.scale(zoom, zoom);
|
|
417
|
-
if (rotation !== 0) {
|
|
418
|
-
c.rotate(-rotation, 0, 0);
|
|
419
|
-
}
|
|
420
|
-
c.translate(-cameraX, -cameraY);
|
|
421
|
-
}
|
|
422
|
-
/** Increment draw call counter. Called by batching systems. */
|
|
423
|
-
recordDrawCall() {
|
|
424
|
-
this.stats.drawCalls++;
|
|
425
|
-
}
|
|
426
|
-
/** Increment batch counter. */
|
|
427
|
-
recordBatch() {
|
|
428
|
-
this.stats.batchCount++;
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
// src/sprite-batch.ts
|
|
433
|
-
var SpriteBatch = class {
|
|
434
|
-
_commands = [];
|
|
435
|
-
_pool = [];
|
|
436
|
-
_batches = [];
|
|
437
|
-
/** Number of queued draw commands. */
|
|
438
|
-
get count() {
|
|
439
|
-
return this._commands.length;
|
|
440
|
-
}
|
|
441
|
-
/** Queue a sprite draw from a frame.
|
|
442
|
-
*
|
|
443
|
-
* `anchorX` / `anchorY` define the pivot point of the sprite as a
|
|
444
|
-
* fraction of its destination size — `(0, 0)` = top-left (default),
|
|
445
|
-
* `(0.5, 0.5)` = center, `(1, 1)` = bottom-right. `dstX` / `dstY`
|
|
446
|
-
* then refer to the anchor point instead of the top-left corner.
|
|
447
|
-
*
|
|
448
|
-
* `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */
|
|
449
|
-
draw(image, frame, dstX, dstY, dstW, dstH, effect, anchorX = 0, anchorY = 0, alpha = 1, rotation = 0, stretch = false) {
|
|
450
|
-
const cmd = this._acquire();
|
|
451
|
-
const w = dstW ?? frame.w;
|
|
452
|
-
const h = dstH ?? frame.h;
|
|
453
|
-
cmd.image = image;
|
|
454
|
-
cmd.srcX = frame.x;
|
|
455
|
-
cmd.srcY = frame.y;
|
|
456
|
-
cmd.srcW = frame.w;
|
|
457
|
-
cmd.srcH = frame.h;
|
|
458
|
-
cmd.dstX = dstX - anchorX * w;
|
|
459
|
-
cmd.dstY = dstY - anchorY * h;
|
|
460
|
-
cmd.dstW = w;
|
|
461
|
-
cmd.dstH = h;
|
|
462
|
-
cmd.effect = effect;
|
|
463
|
-
cmd.alpha = alpha;
|
|
464
|
-
cmd.rotation = rotation;
|
|
465
|
-
cmd.anchorX = anchorX;
|
|
466
|
-
cmd.anchorY = anchorY;
|
|
467
|
-
cmd.stretch = stretch;
|
|
468
|
-
this._commands.push(cmd);
|
|
469
|
-
}
|
|
470
|
-
/** Queue a raw sprite draw with explicit src/dst rects. `dstX` / `dstY`
|
|
471
|
-
* refer to the anchor point — offset by `anchorX * dstW` / `anchorY * dstH`.
|
|
472
|
-
*
|
|
473
|
-
* `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */
|
|
474
|
-
drawRaw(image, srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH, effect, anchorX = 0, anchorY = 0, alpha = 1, rotation = 0, stretch = false) {
|
|
475
|
-
const cmd = this._acquire();
|
|
476
|
-
cmd.image = image;
|
|
477
|
-
cmd.srcX = srcX;
|
|
478
|
-
cmd.srcY = srcY;
|
|
479
|
-
cmd.srcW = srcW;
|
|
480
|
-
cmd.srcH = srcH;
|
|
481
|
-
cmd.dstX = dstX - anchorX * dstW;
|
|
482
|
-
cmd.dstY = dstY - anchorY * dstH;
|
|
483
|
-
cmd.dstW = dstW;
|
|
484
|
-
cmd.dstH = dstH;
|
|
485
|
-
cmd.effect = effect;
|
|
486
|
-
cmd.alpha = alpha;
|
|
487
|
-
cmd.rotation = rotation;
|
|
488
|
-
cmd.anchorX = anchorX;
|
|
489
|
-
cmd.anchorY = anchorY;
|
|
490
|
-
cmd.stretch = stretch;
|
|
491
|
-
this._commands.push(cmd);
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* Flush all queued commands, grouped by (texture, effect, alpha) identity.
|
|
495
|
-
*
|
|
496
|
-
* @param drawAtlas Callback invoked once per batch. Receives
|
|
497
|
-
* `(image, commands)` — all commands in a batch share the same texture,
|
|
498
|
-
* the same effect (readable from `commands[0].effect`), and the same
|
|
499
|
-
* alpha (readable from `commands[0].alpha`).
|
|
500
|
-
* @param renderer Optional renderer to record stats.
|
|
501
|
-
*/
|
|
502
|
-
flush(drawAtlas, renderer) {
|
|
503
|
-
if (this._commands.length === 0) return;
|
|
504
|
-
const batches = this._batches;
|
|
505
|
-
for (const cmd of this._commands) {
|
|
506
|
-
let batch;
|
|
507
|
-
for (let i = 0; i < batches.length; i++) {
|
|
508
|
-
const b = batches[i];
|
|
509
|
-
if (b.image === cmd.image && b.effect === cmd.effect && b.alpha === cmd.alpha) {
|
|
510
|
-
batch = b;
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
if (!batch) {
|
|
515
|
-
batch = { image: cmd.image, effect: cmd.effect, alpha: cmd.alpha, commands: [] };
|
|
516
|
-
batches.push(batch);
|
|
517
|
-
}
|
|
518
|
-
batch.commands.push(cmd);
|
|
519
|
-
}
|
|
520
|
-
for (let i = 0; i < batches.length; i++) {
|
|
521
|
-
const batch = batches[i];
|
|
522
|
-
drawAtlas(batch.image, batch.commands);
|
|
523
|
-
if (renderer) {
|
|
524
|
-
renderer.recordDrawCall();
|
|
525
|
-
renderer.recordBatch();
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
for (const cmd of this._commands) {
|
|
529
|
-
this._pool.push(cmd);
|
|
530
|
-
}
|
|
531
|
-
this._commands.length = 0;
|
|
532
|
-
for (let i = 0; i < batches.length; i++) {
|
|
533
|
-
batches[i].commands.length = 0;
|
|
534
|
-
}
|
|
535
|
-
batches.length = 0;
|
|
536
|
-
}
|
|
537
|
-
/** Clear all queued commands without drawing. */
|
|
538
|
-
clear() {
|
|
539
|
-
for (const cmd of this._commands) {
|
|
540
|
-
this._pool.push(cmd);
|
|
541
|
-
}
|
|
542
|
-
this._commands.length = 0;
|
|
543
|
-
}
|
|
544
|
-
_acquire() {
|
|
545
|
-
if (this._pool.length > 0) {
|
|
546
|
-
return this._pool.pop();
|
|
547
|
-
}
|
|
548
|
-
return {
|
|
549
|
-
image: null,
|
|
550
|
-
srcX: 0,
|
|
551
|
-
srcY: 0,
|
|
552
|
-
srcW: 0,
|
|
553
|
-
srcH: 0,
|
|
554
|
-
dstX: 0,
|
|
555
|
-
dstY: 0,
|
|
556
|
-
dstW: 0,
|
|
557
|
-
dstH: 0,
|
|
558
|
-
effect: void 0,
|
|
559
|
-
alpha: 1,
|
|
560
|
-
rotation: 0,
|
|
561
|
-
anchorX: 0,
|
|
562
|
-
anchorY: 0,
|
|
563
|
-
stretch: false
|
|
564
|
-
};
|
|
565
|
-
}
|
|
74
|
+
/** Camera position (world-space center). */
|
|
75
|
+
x = 0;
|
|
76
|
+
y = 0;
|
|
77
|
+
/** Zoom level (1 = 100%). */
|
|
78
|
+
zoom = 1;
|
|
79
|
+
/** Rotation in radians. */
|
|
80
|
+
rotation = 0;
|
|
81
|
+
/** Viewport width in pixels. */
|
|
82
|
+
viewportWidth = 0;
|
|
83
|
+
/** Viewport height in pixels. */
|
|
84
|
+
viewportHeight = 0;
|
|
85
|
+
/** Optional world bounds to clamp camera position. */
|
|
86
|
+
bounds = null;
|
|
87
|
+
_follow = null;
|
|
88
|
+
_shake = null;
|
|
89
|
+
_shakePerlin = null;
|
|
90
|
+
_shakeSeed = 0;
|
|
91
|
+
_shakePerlinSeed = 0;
|
|
92
|
+
_motionScale = 1;
|
|
93
|
+
/** Set viewport dimensions. */
|
|
94
|
+
setViewport(width, height) {
|
|
95
|
+
this.viewportWidth = width;
|
|
96
|
+
this.viewportHeight = height;
|
|
97
|
+
}
|
|
98
|
+
/** Start following a target position. */
|
|
99
|
+
follow(target, options) {
|
|
100
|
+
this._follow = {
|
|
101
|
+
target,
|
|
102
|
+
options: options ?? {}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Stop following. */
|
|
106
|
+
unfollow() {
|
|
107
|
+
this._follow = null;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Global motion multiplier applied to subsequent `shake()` calls. Scales
|
|
111
|
+
* both amplitude and duration. Default 1 (no-op). 0 fully suppresses
|
|
112
|
+
* shake. Intended to be driven by `@flare-engine/a11y` reduced-motion so
|
|
113
|
+
* callers don't have to guard every shake site.
|
|
114
|
+
*
|
|
115
|
+
* Only affects future `shake()` calls — does not retroactively modify an
|
|
116
|
+
* in-flight shake.
|
|
117
|
+
*
|
|
118
|
+
* Clamped to `>= 0`.
|
|
119
|
+
*/
|
|
120
|
+
setMotionScale(scale) {
|
|
121
|
+
this._motionScale = scale < 0 ? 0 : scale;
|
|
122
|
+
}
|
|
123
|
+
/** Current motion scale (default 1). */
|
|
124
|
+
get motionScale() {
|
|
125
|
+
return this._motionScale;
|
|
126
|
+
}
|
|
127
|
+
/** Start camera shake. */
|
|
128
|
+
shake(intensity, duration) {
|
|
129
|
+
const k = this._motionScale;
|
|
130
|
+
if (k <= 0) {
|
|
131
|
+
this._shake = null;
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
this._shake = {
|
|
135
|
+
intensity: intensity * k,
|
|
136
|
+
duration: duration * k,
|
|
137
|
+
elapsed: 0,
|
|
138
|
+
offsetX: 0,
|
|
139
|
+
offsetY: 0
|
|
140
|
+
};
|
|
141
|
+
this._shakeSeed++;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Start a Perlin-noise-based camera shake. Overwrites any in-flight
|
|
145
|
+
* shakePerlin (same semantics as shake()). Respects motionScale.
|
|
146
|
+
*/
|
|
147
|
+
shakePerlin(intensity, duration) {
|
|
148
|
+
const k = this._motionScale;
|
|
149
|
+
if (k <= 0) {
|
|
150
|
+
this._shakePerlin = null;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this._shakePerlin = {
|
|
154
|
+
intensity: intensity * k,
|
|
155
|
+
duration: duration * k,
|
|
156
|
+
elapsed: 0,
|
|
157
|
+
offsetX: 0,
|
|
158
|
+
offsetY: 0
|
|
159
|
+
};
|
|
160
|
+
this._shakePerlinSeed++;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Update camera. Call once per frame.
|
|
164
|
+
* @param dt Delta time in seconds.
|
|
165
|
+
*/
|
|
166
|
+
update(dt) {
|
|
167
|
+
if (this._follow) {
|
|
168
|
+
const { target, options } = this._follow;
|
|
169
|
+
const smoothing = options.smoothing ?? .1;
|
|
170
|
+
const offsetX = options.offset?.x ?? 0;
|
|
171
|
+
const offsetY = options.offset?.y ?? 0;
|
|
172
|
+
const goalX = target.x + offsetX;
|
|
173
|
+
const goalY = target.y + offsetY;
|
|
174
|
+
if (options.deadZone) {
|
|
175
|
+
const dz = options.deadZone;
|
|
176
|
+
const relX = target.x - this.x;
|
|
177
|
+
const relY = target.y - this.y;
|
|
178
|
+
if (relX < dz.x) this.x += (relX + offsetX - dz.x) * (1 - smoothing ** dt);
|
|
179
|
+
else if (relX > dz.x + dz.width) this.x += (relX + offsetX - dz.x - dz.width) * (1 - smoothing ** dt);
|
|
180
|
+
if (relY < dz.y) this.y += (relY + offsetY - dz.y) * (1 - smoothing ** dt);
|
|
181
|
+
else if (relY > dz.y + dz.height) this.y += (relY + offsetY - dz.y - dz.height) * (1 - smoothing ** dt);
|
|
182
|
+
} else {
|
|
183
|
+
const factor = 1 - smoothing ** dt;
|
|
184
|
+
this.x += (goalX - this.x) * factor;
|
|
185
|
+
this.y += (goalY - this.y) * factor;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (this.bounds) this._clampToBounds();
|
|
189
|
+
if (this._shake) {
|
|
190
|
+
this._shake.elapsed += dt;
|
|
191
|
+
if (this._shake.elapsed >= this._shake.duration) this._shake = null;
|
|
192
|
+
else {
|
|
193
|
+
const decay = 1 - this._shake.elapsed / this._shake.duration;
|
|
194
|
+
const seed = this._shakeSeed + this._shake.elapsed * 100;
|
|
195
|
+
this._shake.offsetX = (pseudoRandom(seed) * 2 - 1) * this._shake.intensity * decay;
|
|
196
|
+
this._shake.offsetY = (pseudoRandom(seed + 1) * 2 - 1) * this._shake.intensity * decay;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (this._shakePerlin) {
|
|
200
|
+
this._shakePerlin.elapsed += dt;
|
|
201
|
+
if (this._shakePerlin.elapsed >= this._shakePerlin.duration) this._shakePerlin = null;
|
|
202
|
+
else {
|
|
203
|
+
const decay = 1 - this._shakePerlin.elapsed / this._shakePerlin.duration;
|
|
204
|
+
const t = this._shakePerlin.elapsed * 4.7 + this._shakePerlinSeed * 17.3;
|
|
205
|
+
this._shakePerlin.offsetX = fbmNoise2d(t, 0) * this._shakePerlin.intensity * decay;
|
|
206
|
+
this._shakePerlin.offsetY = fbmNoise2d(0, t + 5.4) * this._shakePerlin.intensity * decay;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Get the effective camera X (including shake + shakePerlin offsets). */
|
|
211
|
+
get effectiveX() {
|
|
212
|
+
return this.x + (this._shake?.offsetX ?? 0) + (this._shakePerlin?.offsetX ?? 0);
|
|
213
|
+
}
|
|
214
|
+
/** Get the effective camera Y (including shake + shakePerlin offsets). */
|
|
215
|
+
get effectiveY() {
|
|
216
|
+
return this.y + (this._shake?.offsetY ?? 0) + (this._shakePerlin?.offsetY ?? 0);
|
|
217
|
+
}
|
|
218
|
+
/** Convert world coordinates to screen coordinates. */
|
|
219
|
+
worldToScreen(worldX, worldY) {
|
|
220
|
+
return this.worldToScreenInto(worldX, worldY, {
|
|
221
|
+
x: 0,
|
|
222
|
+
y: 0
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/** Zero-alloc variant — writes screen coordinates into `out`. */
|
|
226
|
+
worldToScreenInto(worldX, worldY, out) {
|
|
227
|
+
const cos = Math.cos(-this.rotation);
|
|
228
|
+
const sin = Math.sin(-this.rotation);
|
|
229
|
+
const dx = (worldX - this.effectiveX) * this.zoom;
|
|
230
|
+
const dy = (worldY - this.effectiveY) * this.zoom;
|
|
231
|
+
out.x = cos * dx - sin * dy + this.viewportWidth / 2;
|
|
232
|
+
out.y = sin * dx + cos * dy + this.viewportHeight / 2;
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
/** Convert screen coordinates to world coordinates. */
|
|
236
|
+
screenToWorld(screenX, screenY) {
|
|
237
|
+
return this.screenToWorldInto(screenX, screenY, {
|
|
238
|
+
x: 0,
|
|
239
|
+
y: 0
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/** Zero-alloc variant — writes world coordinates into `out`. */
|
|
243
|
+
screenToWorldInto(screenX, screenY, out) {
|
|
244
|
+
const cx = screenX - this.viewportWidth / 2;
|
|
245
|
+
const cy = screenY - this.viewportHeight / 2;
|
|
246
|
+
const cos = Math.cos(this.rotation);
|
|
247
|
+
const sin = Math.sin(this.rotation);
|
|
248
|
+
const rx = cos * cx - sin * cy;
|
|
249
|
+
const ry = sin * cx + cos * cy;
|
|
250
|
+
out.x = rx / this.zoom + this.effectiveX;
|
|
251
|
+
out.y = ry / this.zoom + this.effectiveY;
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
/** Get the visible world-space bounding rect (allocates a fresh Rect). */
|
|
255
|
+
getBounds() {
|
|
256
|
+
return this.getBoundsInto({
|
|
257
|
+
x: 0,
|
|
258
|
+
y: 0,
|
|
259
|
+
width: 0,
|
|
260
|
+
height: 0
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
/** Zero-alloc variant — writes the visible bounds into `out`. */
|
|
264
|
+
getBoundsInto(out) {
|
|
265
|
+
const halfW = this.viewportWidth / (2 * this.zoom);
|
|
266
|
+
const halfH = this.viewportHeight / (2 * this.zoom);
|
|
267
|
+
out.x = this.effectiveX - halfW;
|
|
268
|
+
out.y = this.effectiveY - halfH;
|
|
269
|
+
out.width = halfW * 2;
|
|
270
|
+
out.height = halfH * 2;
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
_clampToBounds() {
|
|
274
|
+
const b = this.bounds;
|
|
275
|
+
const halfW = this.viewportWidth / (2 * this.zoom);
|
|
276
|
+
const halfH = this.viewportHeight / (2 * this.zoom);
|
|
277
|
+
const minX = b.x + halfW;
|
|
278
|
+
const maxX = b.x + b.width - halfW;
|
|
279
|
+
const minY = b.y + halfH;
|
|
280
|
+
const maxY = b.y + b.height - halfH;
|
|
281
|
+
if (minX <= maxX) this.x = Math.max(minX, Math.min(maxX, this.x));
|
|
282
|
+
else this.x = b.x + b.width / 2;
|
|
283
|
+
if (minY <= maxY) this.y = Math.max(minY, Math.min(maxY, this.y));
|
|
284
|
+
else this.y = b.y + b.height / 2;
|
|
285
|
+
}
|
|
566
286
|
};
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
type: "fire",
|
|
593
|
-
color: opts.color,
|
|
594
|
-
tipColor: opts.tipColor,
|
|
595
|
-
speed: opts.speed ?? 1,
|
|
596
|
-
intensity: opts.intensity ?? 0.8
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
function dropShadow(opts) {
|
|
600
|
-
return {
|
|
601
|
-
type: "drop-shadow",
|
|
602
|
-
color: opts.color,
|
|
603
|
-
offsetX: opts.offsetX ?? 0,
|
|
604
|
-
offsetY: opts.offsetY ?? 6,
|
|
605
|
-
blur: opts.blur ?? 4
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
function roofsCity(opts) {
|
|
609
|
-
return {
|
|
610
|
-
type: "roofs-city",
|
|
611
|
-
skyColor: opts.skyColor,
|
|
612
|
-
roofColor: opts.roofColor,
|
|
613
|
-
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
614
|
-
density: opts.density ?? 6,
|
|
615
|
-
resolutionW: opts.resolutionW ?? 0,
|
|
616
|
-
resolutionH: opts.resolutionH ?? 0
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
function forestCanopy(opts) {
|
|
620
|
-
return {
|
|
621
|
-
type: "forest-canopy",
|
|
622
|
-
skyColor: opts.skyColor,
|
|
623
|
-
leafColor: opts.leafColor,
|
|
624
|
-
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
625
|
-
density: opts.density ?? 5,
|
|
626
|
-
resolutionW: opts.resolutionW ?? 0,
|
|
627
|
-
resolutionH: opts.resolutionH ?? 0
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
function riverWave(opts) {
|
|
631
|
-
return {
|
|
632
|
-
type: "river-wave",
|
|
633
|
-
waterColor: opts.waterColor,
|
|
634
|
-
foamColor: opts.foamColor,
|
|
635
|
-
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
636
|
-
waveScale: opts.waveScale ?? 2,
|
|
637
|
-
resolutionW: opts.resolutionW ?? 0,
|
|
638
|
-
resolutionH: opts.resolutionH ?? 0
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
function moonCrater(opts) {
|
|
642
|
-
return {
|
|
643
|
-
type: "moon-crater",
|
|
644
|
-
moonColor: opts.moonColor,
|
|
645
|
-
craterColor: opts.craterColor,
|
|
646
|
-
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
647
|
-
craterCount: opts.craterCount ?? 4,
|
|
648
|
-
resolutionW: opts.resolutionW ?? 0,
|
|
649
|
-
resolutionH: opts.resolutionH ?? 0
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
function caveInterior(opts) {
|
|
653
|
-
return {
|
|
654
|
-
type: "cave-interior",
|
|
655
|
-
caveColor: opts.caveColor,
|
|
656
|
-
glowColor: opts.glowColor,
|
|
657
|
-
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
658
|
-
stalactiteCount: opts.stalactiteCount ?? 8,
|
|
659
|
-
resolutionW: opts.resolutionW ?? 0,
|
|
660
|
-
resolutionH: opts.resolutionH ?? 0
|
|
661
|
-
};
|
|
662
|
-
}
|
|
663
|
-
function mountainHills(opts) {
|
|
664
|
-
return {
|
|
665
|
-
type: "mountain-hills",
|
|
666
|
-
skyColor: opts.skyColor,
|
|
667
|
-
hillColor: opts.hillColor,
|
|
668
|
-
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
669
|
-
ridgeScale: opts.ridgeScale ?? 1.5,
|
|
670
|
-
resolutionW: opts.resolutionW ?? 0,
|
|
671
|
-
resolutionH: opts.resolutionH ?? 0
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
function updateEffect(effect, dt) {
|
|
675
|
-
if (effect.type === "flash") {
|
|
676
|
-
const elapsed = effect.elapsed + dt;
|
|
677
|
-
if (elapsed >= effect.duration) return null;
|
|
678
|
-
return { type: "flash", color: effect.color, duration: effect.duration, elapsed };
|
|
679
|
-
}
|
|
680
|
-
return effect;
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/draw-order.ts
|
|
289
|
+
/**
|
|
290
|
+
* Sort an array of drawables by z-index (ascending = back to front).
|
|
291
|
+
* Uses insertion sort which is fast for nearly-sorted arrays
|
|
292
|
+
* (typical in game rendering where order rarely changes).
|
|
293
|
+
*
|
|
294
|
+
* Mutates the input array in place. Returns the same array.
|
|
295
|
+
*
|
|
296
|
+
* @param items The array to sort in place.
|
|
297
|
+
* @param length Optional upper bound — when provided, only sorts `items[0..length)`.
|
|
298
|
+
* Use this when passing a pooled array that may hold stale tail entries.
|
|
299
|
+
*/
|
|
300
|
+
function sortByDrawOrder(items, length) {
|
|
301
|
+
const n = length ?? items.length;
|
|
302
|
+
for (let i = 1; i < n; i++) {
|
|
303
|
+
const item = items[i];
|
|
304
|
+
let j = i - 1;
|
|
305
|
+
while (j >= 0 && items[j].z > item.z) {
|
|
306
|
+
items[j + 1] = items[j];
|
|
307
|
+
j--;
|
|
308
|
+
}
|
|
309
|
+
items[j + 1] = item;
|
|
310
|
+
}
|
|
311
|
+
return items;
|
|
681
312
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
313
|
+
/**
|
|
314
|
+
* Compare function for use with Array.sort when insertion sort isn't desired.
|
|
315
|
+
* Stable relative order for equal z values (returns 0).
|
|
316
|
+
*/
|
|
317
|
+
function drawOrderCompare(a, b) {
|
|
318
|
+
return a.z - b.z;
|
|
686
319
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
/* sksl */
|
|
691
|
-
`
|
|
692
|
-
uniform shader src;
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region src/shaders.ts
|
|
322
|
+
const FIRE_SKSL = `
|
|
693
323
|
uniform float2 resolution;
|
|
694
324
|
uniform float3 color;
|
|
695
325
|
uniform float3 tipColor;
|
|
@@ -715,21 +345,15 @@ float fbm(float2 p) {
|
|
|
715
345
|
}
|
|
716
346
|
|
|
717
347
|
half4 main(float2 coord) {
|
|
718
|
-
half4 srcColor = src.eval(coord);
|
|
719
348
|
float2 uv = coord / max(resolution, float2(1.0));
|
|
720
349
|
float2 np = float2(uv.x * 4.0, uv.y * 6.0 - time * speed * 2.0);
|
|
721
350
|
float n = fbm(np);
|
|
722
351
|
float flameMask = smoothstep(0.0, intensity, n * (1.0 - uv.y));
|
|
723
352
|
float3 fireRgb = mix(color, tipColor, uv.y);
|
|
724
|
-
|
|
725
|
-
return half4(fireColor.rgb, fireColor.a * srcColor.a);
|
|
353
|
+
return half4(fireRgb.r, fireRgb.g, fireRgb.b, flameMask);
|
|
726
354
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
var FOG_SKSL = (
|
|
730
|
-
/* sksl */
|
|
731
|
-
`
|
|
732
|
-
uniform shader src;
|
|
355
|
+
`;
|
|
356
|
+
const FOG_SKSL = `
|
|
733
357
|
uniform float2 resolution;
|
|
734
358
|
uniform float3 color;
|
|
735
359
|
uniform float density;
|
|
@@ -767,16 +391,10 @@ half4 main(float2 coord) {
|
|
|
767
391
|
float2 uv = coord / max(resolution, float2(1.0));
|
|
768
392
|
float2 drift = float2(time * speed, 0.3 * time * speed);
|
|
769
393
|
float n = clamp(fbm(uv * scale + drift), 0.0, 1.0);
|
|
770
|
-
half4
|
|
771
|
-
half3 blended = mix(srcColor.rgb, half3(color.r, color.g, color.b), half(density * n));
|
|
772
|
-
return half4(blended, srcColor.a);
|
|
394
|
+
return half4(color.r, color.g, color.b, clamp(density * n, 0.0, 1.0));
|
|
773
395
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
var WATER_SKSL = (
|
|
777
|
-
/* sksl */
|
|
778
|
-
`
|
|
779
|
-
uniform shader src;
|
|
396
|
+
`;
|
|
397
|
+
const WATER_SKSL = `
|
|
780
398
|
uniform float2 resolution;
|
|
781
399
|
uniform float3 color;
|
|
782
400
|
uniform float3 foamColor;
|
|
@@ -785,25 +403,16 @@ uniform float waveAmplitude;
|
|
|
785
403
|
uniform float time;
|
|
786
404
|
|
|
787
405
|
half4 main(float2 coord) {
|
|
788
|
-
float2 uv = coord / resolution;
|
|
789
|
-
float wave1 = sin(uv.x * 6.2832 * 3.0 + time * waveSpeed * 2.0) * waveAmplitude;
|
|
790
|
-
float wave2 = sin(uv.x * 6.2832 * 5.0 - time * waveSpeed * 1.3 + 1.5) * waveAmplitude * 0.5;
|
|
791
|
-
float totalWave = wave1 + wave2;
|
|
792
|
-
float2 displaced = float2(uv.x, uv.y + totalWave);
|
|
793
|
-
displaced = clamp(displaced, float2(0.0), float2(1.0));
|
|
794
|
-
half4 c = src.eval(displaced * resolution);
|
|
406
|
+
float2 uv = coord / max(resolution, float2(1.0));
|
|
795
407
|
float waveDeriv = cos(uv.x * 6.2832 * 3.0 + time * waveSpeed * 2.0) * waveAmplitude * 2.0
|
|
796
408
|
+ cos(uv.x * 6.2832 * 5.0 - time * waveSpeed * 1.3 + 1.5) * waveAmplitude;
|
|
797
409
|
float foam = smoothstep(waveAmplitude * 0.6, waveAmplitude * 1.5, waveDeriv);
|
|
798
|
-
half3 tinted =
|
|
410
|
+
half3 tinted = half3(color.r, color.g, color.b);
|
|
799
411
|
half3 final = mix(tinted, half3(foamColor.r, foamColor.g, foamColor.b), half(foam));
|
|
800
|
-
return half4(final,
|
|
412
|
+
return half4(final, 1.0);
|
|
801
413
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
var ROOFS_CITY_SKSL = (
|
|
805
|
-
/* sksl */
|
|
806
|
-
`
|
|
414
|
+
`;
|
|
415
|
+
const ROOFS_CITY_SKSL = `
|
|
807
416
|
uniform float2 resolution;
|
|
808
417
|
uniform float3 skyColor;
|
|
809
418
|
uniform float3 roofColor;
|
|
@@ -830,11 +439,8 @@ half4 main(float2 coord) {
|
|
|
830
439
|
half(buildings));
|
|
831
440
|
return half4(col, 1.0);
|
|
832
441
|
}
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
var FOREST_CANOPY_SKSL = (
|
|
836
|
-
/* sksl */
|
|
837
|
-
`
|
|
442
|
+
`;
|
|
443
|
+
const FOREST_CANOPY_SKSL = `
|
|
838
444
|
uniform float2 resolution;
|
|
839
445
|
uniform float3 skyColor;
|
|
840
446
|
uniform float3 leafColor;
|
|
@@ -862,11 +468,8 @@ half4 main(float2 coord) {
|
|
|
862
468
|
half(canopy));
|
|
863
469
|
return half4(col, 1.0);
|
|
864
470
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
var RIVER_WAVE_SKSL = (
|
|
868
|
-
/* sksl */
|
|
869
|
-
`
|
|
471
|
+
`;
|
|
472
|
+
const RIVER_WAVE_SKSL = `
|
|
870
473
|
uniform float2 resolution;
|
|
871
474
|
uniform float3 waterColor;
|
|
872
475
|
uniform float3 foamColor;
|
|
@@ -889,11 +492,8 @@ half4 main(float2 coord) {
|
|
|
889
492
|
half3 col = mix(base, half3(foamColor.r, foamColor.g, foamColor.b), half(crest * 0.6));
|
|
890
493
|
return half4(col, 1.0);
|
|
891
494
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
var MOON_CRATER_SKSL = (
|
|
895
|
-
/* sksl */
|
|
896
|
-
`
|
|
495
|
+
`;
|
|
496
|
+
const MOON_CRATER_SKSL = `
|
|
897
497
|
uniform float2 resolution;
|
|
898
498
|
uniform float3 moonColor;
|
|
899
499
|
uniform float3 craterColor;
|
|
@@ -925,11 +525,8 @@ half4 main(float2 coord) {
|
|
|
925
525
|
half(c));
|
|
926
526
|
return half4(col, 1.0);
|
|
927
527
|
}
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
var CAVE_INTERIOR_SKSL = (
|
|
931
|
-
/* sksl */
|
|
932
|
-
`
|
|
528
|
+
`;
|
|
529
|
+
const CAVE_INTERIOR_SKSL = `
|
|
933
530
|
uniform float2 resolution;
|
|
934
531
|
uniform float3 caveColor;
|
|
935
532
|
uniform float3 glowColor;
|
|
@@ -959,11 +556,8 @@ half4 main(float2 coord) {
|
|
|
959
556
|
col = mix(col, half3(caveColor.r, caveColor.g, caveColor.b) * half(0.3), half(stalac));
|
|
960
557
|
return half4(col, 1.0);
|
|
961
558
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
var MOUNTAIN_HILLS_SKSL = (
|
|
965
|
-
/* sksl */
|
|
966
|
-
`
|
|
559
|
+
`;
|
|
560
|
+
const MOUNTAIN_HILLS_SKSL = `
|
|
967
561
|
uniform float2 resolution;
|
|
968
562
|
uniform float3 skyColor;
|
|
969
563
|
uniform float3 hillColor;
|
|
@@ -997,781 +591,1234 @@ half4 main(float2 coord) {
|
|
|
997
591
|
half(mountains));
|
|
998
592
|
return half4(col, 1.0);
|
|
999
593
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
594
|
+
`;
|
|
595
|
+
//#endregion
|
|
596
|
+
//#region src/sprite-effects.ts
|
|
597
|
+
/** Create a tint effect. */
|
|
598
|
+
function tint(color, amount) {
|
|
599
|
+
return {
|
|
600
|
+
type: "tint",
|
|
601
|
+
color,
|
|
602
|
+
amount
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/** Create a flash effect. */
|
|
606
|
+
function flash(color, durationSec) {
|
|
607
|
+
return {
|
|
608
|
+
type: "flash",
|
|
609
|
+
color,
|
|
610
|
+
duration: durationSec,
|
|
611
|
+
elapsed: 0
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
/** Create a grayscale effect. */
|
|
615
|
+
function grayscale(amount) {
|
|
616
|
+
return {
|
|
617
|
+
type: "grayscale",
|
|
618
|
+
amount
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
/** Create an outline effect. */
|
|
622
|
+
function outline(color, width) {
|
|
623
|
+
return {
|
|
624
|
+
type: "outline",
|
|
625
|
+
color,
|
|
626
|
+
width
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
/** Create a custom shader effect. `shader` must be pre-compiled; compiling
|
|
630
|
+
* SkSL per frame is expensive. `uniforms` may be mutated externally between
|
|
631
|
+
* frames to animate parameters. */
|
|
632
|
+
function custom(shader, uniforms) {
|
|
633
|
+
return {
|
|
634
|
+
type: "custom",
|
|
635
|
+
shader,
|
|
636
|
+
uniforms
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
/** Create a water surface effect. Time is passed to the renderer each frame
|
|
640
|
+
* via `beginFrame(w, h, time)` — the effect itself is time-invariant. */
|
|
641
|
+
function water(color, foamColor, waveSpeed, waveAmplitude) {
|
|
642
|
+
return {
|
|
643
|
+
type: "water",
|
|
644
|
+
color,
|
|
645
|
+
foamColor,
|
|
646
|
+
waveSpeed,
|
|
647
|
+
waveAmplitude
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
/** Create a fog / volumetric haze effect. Time is passed to the renderer each
|
|
651
|
+
* frame via `beginFrame(w, h, time)` — the effect itself is time-invariant. */
|
|
652
|
+
function fog(color, density = .5, speed = .3, scale = 2) {
|
|
653
|
+
return {
|
|
654
|
+
type: "fog",
|
|
655
|
+
color,
|
|
656
|
+
density,
|
|
657
|
+
speed,
|
|
658
|
+
scale
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
/** Create a fire / flame effect. Time is passed to the renderer each
|
|
662
|
+
* frame via `beginFrame(w, h, time)` — the effect itself is time-invariant. */
|
|
663
|
+
function fire(opts) {
|
|
664
|
+
return {
|
|
665
|
+
type: "fire",
|
|
666
|
+
color: opts.color,
|
|
667
|
+
tipColor: opts.tipColor,
|
|
668
|
+
speed: opts.speed ?? 1,
|
|
669
|
+
intensity: opts.intensity ?? .8
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
/** Create a drop-shadow effect. Defaults to a soft shadow offset downward for
|
|
673
|
+
* grounding; `color.a` is the shadow opacity. */
|
|
674
|
+
function dropShadow(opts) {
|
|
675
|
+
return {
|
|
676
|
+
type: "drop-shadow",
|
|
677
|
+
color: opts.color,
|
|
678
|
+
offsetX: opts.offsetX ?? 0,
|
|
679
|
+
offsetY: opts.offsetY ?? 6,
|
|
680
|
+
blur: opts.blur ?? 4
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
/** Create a rooftop city skyline effect. */
|
|
684
|
+
function roofsCity(opts) {
|
|
685
|
+
return {
|
|
686
|
+
type: "roofs-city",
|
|
687
|
+
skyColor: opts.skyColor,
|
|
688
|
+
roofColor: opts.roofColor,
|
|
689
|
+
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
690
|
+
density: opts.density ?? 6,
|
|
691
|
+
resolutionW: opts.resolutionW ?? 0,
|
|
692
|
+
resolutionH: opts.resolutionH ?? 0
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
/** Create a forest canopy effect. */
|
|
696
|
+
function forestCanopy(opts) {
|
|
697
|
+
return {
|
|
698
|
+
type: "forest-canopy",
|
|
699
|
+
skyColor: opts.skyColor,
|
|
700
|
+
leafColor: opts.leafColor,
|
|
701
|
+
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
702
|
+
density: opts.density ?? 5,
|
|
703
|
+
resolutionW: opts.resolutionW ?? 0,
|
|
704
|
+
resolutionH: opts.resolutionH ?? 0
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
/** Create a river wave surface effect. */
|
|
708
|
+
function riverWave(opts) {
|
|
709
|
+
return {
|
|
710
|
+
type: "river-wave",
|
|
711
|
+
waterColor: opts.waterColor,
|
|
712
|
+
foamColor: opts.foamColor,
|
|
713
|
+
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
714
|
+
waveScale: opts.waveScale ?? 2,
|
|
715
|
+
resolutionW: opts.resolutionW ?? 0,
|
|
716
|
+
resolutionH: opts.resolutionH ?? 0
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
/** Create a moon crater surface effect. */
|
|
720
|
+
function moonCrater(opts) {
|
|
721
|
+
return {
|
|
722
|
+
type: "moon-crater",
|
|
723
|
+
moonColor: opts.moonColor,
|
|
724
|
+
craterColor: opts.craterColor,
|
|
725
|
+
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
726
|
+
craterCount: opts.craterCount ?? 4,
|
|
727
|
+
resolutionW: opts.resolutionW ?? 0,
|
|
728
|
+
resolutionH: opts.resolutionH ?? 0
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
/** Create a cave interior effect. */
|
|
732
|
+
function caveInterior(opts) {
|
|
733
|
+
return {
|
|
734
|
+
type: "cave-interior",
|
|
735
|
+
caveColor: opts.caveColor,
|
|
736
|
+
glowColor: opts.glowColor,
|
|
737
|
+
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
738
|
+
stalactiteCount: opts.stalactiteCount ?? 8,
|
|
739
|
+
resolutionW: opts.resolutionW ?? 0,
|
|
740
|
+
resolutionH: opts.resolutionH ?? 0
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
/** Create a mountain / hill ridge effect. */
|
|
744
|
+
function mountainHills(opts) {
|
|
745
|
+
return {
|
|
746
|
+
type: "mountain-hills",
|
|
747
|
+
skyColor: opts.skyColor,
|
|
748
|
+
hillColor: opts.hillColor,
|
|
749
|
+
scrollOffsetY: opts.scrollOffsetY ?? 0,
|
|
750
|
+
ridgeScale: opts.ridgeScale ?? 1.5,
|
|
751
|
+
resolutionW: opts.resolutionW ?? 0,
|
|
752
|
+
resolutionH: opts.resolutionH ?? 0
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Advance a time-based effect by `dt` seconds.
|
|
757
|
+
*
|
|
758
|
+
* Returns a new effect object if state changed (e.g. flash elapsed time
|
|
759
|
+
* advances), the same reference if the effect is time-invariant (tint,
|
|
760
|
+
* grayscale, outline, custom), or `null` if the effect has expired.
|
|
761
|
+
*
|
|
762
|
+
* Effects are immutable — callers must replace their reference with the
|
|
763
|
+
* returned value. Sharing an effect instance across entities is safe.
|
|
764
|
+
*/
|
|
765
|
+
function updateEffect(effect, dt) {
|
|
766
|
+
if (effect.type === "flash") {
|
|
767
|
+
const elapsed = effect.elapsed + dt;
|
|
768
|
+
if (elapsed >= effect.duration) return null;
|
|
769
|
+
return {
|
|
770
|
+
type: "flash",
|
|
771
|
+
color: effect.color,
|
|
772
|
+
duration: effect.duration,
|
|
773
|
+
elapsed
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
return effect;
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Get the current intensity of a flash effect (1 at start, 0 at end).
|
|
780
|
+
*/
|
|
781
|
+
function flashIntensity(effect) {
|
|
782
|
+
if (effect.duration <= 0) return 0;
|
|
783
|
+
const t = effect.elapsed / effect.duration;
|
|
784
|
+
return Math.max(0, 1 - t);
|
|
785
|
+
}
|
|
786
|
+
//#endregion
|
|
787
|
+
//#region src/frame-renderer.ts
|
|
788
|
+
/** See the dev-flag note at the top of this file. */
|
|
789
|
+
const G = globalThis;
|
|
790
|
+
const BLEND_SRC_OVER = 3;
|
|
791
|
+
const BLEND_SRC_IN = 5;
|
|
792
|
+
const OUTLINE_OFFSETS = [
|
|
793
|
+
[1, 0],
|
|
794
|
+
[-1, 0],
|
|
795
|
+
[0, 1],
|
|
796
|
+
[0, -1],
|
|
797
|
+
[Math.SQRT1_2, Math.SQRT1_2],
|
|
798
|
+
[Math.SQRT1_2, -Math.SQRT1_2],
|
|
799
|
+
[-Math.SQRT1_2, Math.SQRT1_2],
|
|
800
|
+
[-Math.SQRT1_2, -Math.SQRT1_2]
|
|
1015
801
|
];
|
|
802
|
+
let _xformUnrotatedCount = 0;
|
|
803
|
+
let _xformRotatedCount = 0;
|
|
804
|
+
/**
|
|
805
|
+
* Create a `FrameRenderer` backed by the given structural Skia module.
|
|
806
|
+
*
|
|
807
|
+
* Both `@flare-engine/react` (passing the real RN-Skia module) and
|
|
808
|
+
* `@flare-engine/render-web` (passing a CanvasKit shim) use this factory.
|
|
809
|
+
*/
|
|
1016
810
|
function createFrameRenderer(Skia) {
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
811
|
+
const defaultPaint = Skia.Paint();
|
|
812
|
+
let tintPaint = null;
|
|
813
|
+
let flashPaint = null;
|
|
814
|
+
let grayscalePaint = null;
|
|
815
|
+
let outlinePaint = null;
|
|
816
|
+
let customPaint = null;
|
|
817
|
+
let waterPaint = null;
|
|
818
|
+
let waterRuntimeEffect = null;
|
|
819
|
+
let fogPaint = null;
|
|
820
|
+
let fogRuntimeEffect = null;
|
|
821
|
+
let firePaint = null;
|
|
822
|
+
let fireRuntimeEffect = null;
|
|
823
|
+
let dropShadowPaint = null;
|
|
824
|
+
let roofsCityPaint = null;
|
|
825
|
+
let roofsCityEffect = null;
|
|
826
|
+
let forestCanopyPaint = null;
|
|
827
|
+
let forestCanopyEffect = null;
|
|
828
|
+
let riverWavePaint = null;
|
|
829
|
+
let riverWaveEffect = null;
|
|
830
|
+
let moonCraterPaint = null;
|
|
831
|
+
let moonCraterEffect = null;
|
|
832
|
+
let caveInteriorPaint = null;
|
|
833
|
+
let caveInteriorEffect = null;
|
|
834
|
+
let mountainHillsPaint = null;
|
|
835
|
+
let mountainHillsEffect = null;
|
|
836
|
+
const roofsCityUniforms = new Array(10).fill(0);
|
|
837
|
+
const forestCanopyUniforms = new Array(10).fill(0);
|
|
838
|
+
const riverWaveUniforms = new Array(10).fill(0);
|
|
839
|
+
const moonCraterUniforms = new Array(10).fill(0);
|
|
840
|
+
const caveInteriorUniforms = new Array(10).fill(0);
|
|
841
|
+
const mountainHillsUniforms = new Array(10).fill(0);
|
|
842
|
+
const recorder = Skia.PictureRecorder();
|
|
843
|
+
const recordingBounds = Skia.XYWHRect(0, 0, 0, 0);
|
|
844
|
+
const srcRects = [];
|
|
845
|
+
const xforms = [];
|
|
846
|
+
const stretchSrcRects = [];
|
|
847
|
+
const stretchDstRects = [];
|
|
848
|
+
let stretchPaint = null;
|
|
849
|
+
const zoneBgRects = [];
|
|
850
|
+
let _cachedSrcSlice = null;
|
|
851
|
+
let _cachedSrcSliceLen = 0;
|
|
852
|
+
let _cachedXformSlice = null;
|
|
853
|
+
let _cachedXformSliceLen = 0;
|
|
854
|
+
const clearColor = /* @__PURE__ */ new Float32Array(4);
|
|
855
|
+
const tintColor = /* @__PURE__ */ new Float32Array(4);
|
|
856
|
+
const flashColor = /* @__PURE__ */ new Float32Array(4);
|
|
857
|
+
const outlineColor = /* @__PURE__ */ new Float32Array(4);
|
|
858
|
+
const dropShadowColor = /* @__PURE__ */ new Float32Array(4);
|
|
859
|
+
const grayMatrix = [
|
|
860
|
+
1,
|
|
861
|
+
0,
|
|
862
|
+
0,
|
|
863
|
+
0,
|
|
864
|
+
0,
|
|
865
|
+
0,
|
|
866
|
+
1,
|
|
867
|
+
0,
|
|
868
|
+
0,
|
|
869
|
+
0,
|
|
870
|
+
0,
|
|
871
|
+
0,
|
|
872
|
+
1,
|
|
873
|
+
0,
|
|
874
|
+
0,
|
|
875
|
+
0,
|
|
876
|
+
0,
|
|
877
|
+
0,
|
|
878
|
+
1,
|
|
879
|
+
0
|
|
880
|
+
];
|
|
881
|
+
const customUniforms = [];
|
|
882
|
+
const waterUniforms = new Array(11).fill(0);
|
|
883
|
+
const fogUniforms = new Array(9).fill(0);
|
|
884
|
+
const fireUniforms = new Array(11).fill(0);
|
|
885
|
+
let currentFrameTime = 0;
|
|
886
|
+
let currentSpriteW = 0;
|
|
887
|
+
let currentSpriteH = 0;
|
|
888
|
+
let current = null;
|
|
889
|
+
let _boundRuntime = null;
|
|
890
|
+
let _boundChain = null;
|
|
891
|
+
const _loggedOnce = /* @__PURE__ */ new Set();
|
|
892
|
+
function logOnce(msg) {
|
|
893
|
+
if (_loggedOnce.has(msg)) return;
|
|
894
|
+
_loggedOnce.add(msg);
|
|
895
|
+
console.warn(`[postfx] ${msg}`);
|
|
896
|
+
}
|
|
897
|
+
const canvas = {
|
|
898
|
+
save: () => current?.save() ?? 0,
|
|
899
|
+
restore: () => {
|
|
900
|
+
current?.restore();
|
|
901
|
+
},
|
|
902
|
+
translate: (x, y) => {
|
|
903
|
+
current?.translate(x, y);
|
|
904
|
+
},
|
|
905
|
+
scale: (sx, sy) => {
|
|
906
|
+
current?.scale(sx, sy);
|
|
907
|
+
},
|
|
908
|
+
rotate: (degrees, cx, cy) => {
|
|
909
|
+
current?.rotate(degrees, cx, cy);
|
|
910
|
+
},
|
|
911
|
+
clear: (argb) => {
|
|
912
|
+
if (current === null) return;
|
|
913
|
+
clearColor[0] = (argb >>> 16 & 255) / 255;
|
|
914
|
+
clearColor[1] = (argb >>> 8 & 255) / 255;
|
|
915
|
+
clearColor[2] = (argb & 255) / 255;
|
|
916
|
+
clearColor[3] = (argb >>> 24 & 255) / 255;
|
|
917
|
+
current.clear(clearColor);
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
function ensurePoolCapacity(n) {
|
|
921
|
+
while (srcRects.length < n) {
|
|
922
|
+
srcRects.push(Skia.XYWHRect(0, 0, 0, 0));
|
|
923
|
+
xforms.push(Skia.RSXform(1, 0, 0, 0));
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
function ensureStretchPoolCapacity(n) {
|
|
927
|
+
while (stretchSrcRects.length < n) {
|
|
928
|
+
stretchSrcRects.push(Skia.XYWHRect(0, 0, 0, 0));
|
|
929
|
+
stretchDstRects.push(Skia.XYWHRect(0, 0, 0, 0));
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
function ensureZoneBgPoolCapacity(n) {
|
|
933
|
+
while (zoneBgRects.length < n) zoneBgRects.push(Skia.XYWHRect(0, 0, 0, 0));
|
|
934
|
+
}
|
|
935
|
+
function getCachedSrcSlice(n) {
|
|
936
|
+
if (_cachedSrcSlice !== null && _cachedSrcSliceLen === n) return _cachedSrcSlice;
|
|
937
|
+
_cachedSrcSlice = srcRects.length === n ? srcRects : srcRects.slice(0, n);
|
|
938
|
+
_cachedSrcSliceLen = n;
|
|
939
|
+
return _cachedSrcSlice;
|
|
940
|
+
}
|
|
941
|
+
function getCachedXformSlice(n) {
|
|
942
|
+
if (_cachedXformSlice !== null && _cachedXformSliceLen === n) return _cachedXformSlice;
|
|
943
|
+
_cachedXformSlice = xforms.length === n ? xforms : xforms.slice(0, n);
|
|
944
|
+
_cachedXformSliceLen = n;
|
|
945
|
+
return _cachedXformSlice;
|
|
946
|
+
}
|
|
947
|
+
function fillSrcRects(commands, n) {
|
|
948
|
+
for (let i = 0; i < n; i++) {
|
|
949
|
+
const cmd = commands[i];
|
|
950
|
+
srcRects[i]?.setXYWH(cmd.srcX, cmd.srcY, cmd.srcW, cmd.srcH);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function fillXforms(commands, n, dx, dy) {
|
|
954
|
+
for (let i = 0; i < n; i++) {
|
|
955
|
+
const cmd = commands[i];
|
|
956
|
+
const scale = cmd.srcW === 0 ? 1 : cmd.dstW / cmd.srcW;
|
|
957
|
+
if (cmd.rotation === 0) {
|
|
958
|
+
xforms[i]?.set(scale, 0, cmd.dstX + dx, cmd.dstY + dy);
|
|
959
|
+
if (G.__DEV__) _xformUnrotatedCount++;
|
|
960
|
+
} else {
|
|
961
|
+
const c = Math.cos(cmd.rotation);
|
|
962
|
+
const s = Math.sin(cmd.rotation);
|
|
963
|
+
const scos = scale * c;
|
|
964
|
+
const ssin = scale * s;
|
|
965
|
+
const px = cmd.dstX + cmd.anchorX * cmd.dstW + dx;
|
|
966
|
+
const py = cmd.dstY + cmd.anchorY * cmd.dstH + dy;
|
|
967
|
+
const axSrc = cmd.anchorX * cmd.srcW;
|
|
968
|
+
const aySrc = cmd.anchorY * cmd.srcH;
|
|
969
|
+
xforms[i]?.set(scos, ssin, px - scos * axSrc + ssin * aySrc, py - ssin * axSrc - scos * aySrc);
|
|
970
|
+
if (G.__DEV__) _xformRotatedCount++;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
function applyTint(effect) {
|
|
975
|
+
if (tintPaint === null) tintPaint = Skia.Paint();
|
|
976
|
+
writeColor(tintColor, effect.color, effect.amount);
|
|
977
|
+
tintPaint.setColorFilter(Skia.ColorFilter.MakeBlend(tintColor, BLEND_SRC_OVER));
|
|
978
|
+
return tintPaint;
|
|
979
|
+
}
|
|
980
|
+
function applyFlash(effect) {
|
|
981
|
+
if (flashPaint === null) flashPaint = Skia.Paint();
|
|
982
|
+
writeColor(flashColor, effect.color, flashIntensity(effect));
|
|
983
|
+
flashPaint.setColorFilter(Skia.ColorFilter.MakeBlend(flashColor, BLEND_SRC_OVER));
|
|
984
|
+
return flashPaint;
|
|
985
|
+
}
|
|
986
|
+
function applyGrayscale(effect) {
|
|
987
|
+
if (grayscalePaint === null) grayscalePaint = Skia.Paint();
|
|
988
|
+
writeGrayscaleMatrix(grayMatrix, effect.amount);
|
|
989
|
+
grayscalePaint.setColorFilter(Skia.ColorFilter.MakeMatrix(grayMatrix));
|
|
990
|
+
return grayscalePaint;
|
|
991
|
+
}
|
|
992
|
+
function applyOutlinePaint(effect) {
|
|
993
|
+
if (outlinePaint === null) outlinePaint = Skia.Paint();
|
|
994
|
+
writeColor(outlineColor, effect.color, 1);
|
|
995
|
+
outlinePaint.setColorFilter(Skia.ColorFilter.MakeBlend(outlineColor, BLEND_SRC_IN));
|
|
996
|
+
return outlinePaint;
|
|
997
|
+
}
|
|
998
|
+
function applyCustom(effect) {
|
|
999
|
+
if (customPaint === null) customPaint = Skia.Paint();
|
|
1000
|
+
const shader = effect.shader;
|
|
1001
|
+
const floatCount = shader.getUniformFloatCount();
|
|
1002
|
+
while (customUniforms.length < floatCount) customUniforms.push(0);
|
|
1003
|
+
customUniforms.length = floatCount;
|
|
1004
|
+
fillCustomUniforms(effect, shader, customUniforms);
|
|
1005
|
+
customPaint.setShader(shader.makeShader(customUniforms));
|
|
1006
|
+
return customPaint;
|
|
1007
|
+
}
|
|
1008
|
+
function applyWater(effect, time) {
|
|
1009
|
+
if (Skia.RuntimeEffect === void 0) return defaultPaint;
|
|
1010
|
+
if (waterRuntimeEffect === null) {
|
|
1011
|
+
const compiled = Skia.RuntimeEffect.Make(WATER_SKSL);
|
|
1012
|
+
if (compiled === null) return defaultPaint;
|
|
1013
|
+
waterRuntimeEffect = compiled;
|
|
1014
|
+
}
|
|
1015
|
+
if (waterPaint === null) waterPaint = Skia.Paint();
|
|
1016
|
+
waterUniforms[0] = currentSpriteW;
|
|
1017
|
+
waterUniforms[1] = currentSpriteH;
|
|
1018
|
+
waterUniforms[2] = effect.color.r;
|
|
1019
|
+
waterUniforms[3] = effect.color.g;
|
|
1020
|
+
waterUniforms[4] = effect.color.b;
|
|
1021
|
+
waterUniforms[5] = effect.foamColor.r;
|
|
1022
|
+
waterUniforms[6] = effect.foamColor.g;
|
|
1023
|
+
waterUniforms[7] = effect.foamColor.b;
|
|
1024
|
+
waterUniforms[8] = effect.waveSpeed;
|
|
1025
|
+
waterUniforms[9] = effect.waveAmplitude;
|
|
1026
|
+
waterUniforms[10] = time;
|
|
1027
|
+
waterPaint.setShader(waterRuntimeEffect.makeShader(waterUniforms));
|
|
1028
|
+
return waterPaint;
|
|
1029
|
+
}
|
|
1030
|
+
function applyFog(effect, time) {
|
|
1031
|
+
if (Skia.RuntimeEffect === void 0) return defaultPaint;
|
|
1032
|
+
if (fogRuntimeEffect === null) {
|
|
1033
|
+
const compiled = Skia.RuntimeEffect.Make(FOG_SKSL);
|
|
1034
|
+
if (compiled === null) return defaultPaint;
|
|
1035
|
+
fogRuntimeEffect = compiled;
|
|
1036
|
+
}
|
|
1037
|
+
if (fogPaint === null) fogPaint = Skia.Paint();
|
|
1038
|
+
fogUniforms[0] = currentSpriteW;
|
|
1039
|
+
fogUniforms[1] = currentSpriteH;
|
|
1040
|
+
fogUniforms[2] = effect.color.r;
|
|
1041
|
+
fogUniforms[3] = effect.color.g;
|
|
1042
|
+
fogUniforms[4] = effect.color.b;
|
|
1043
|
+
fogUniforms[5] = effect.density;
|
|
1044
|
+
fogUniforms[6] = effect.speed;
|
|
1045
|
+
fogUniforms[7] = effect.scale;
|
|
1046
|
+
fogUniforms[8] = time;
|
|
1047
|
+
fogPaint.setShader(fogRuntimeEffect.makeShader(fogUniforms));
|
|
1048
|
+
return fogPaint;
|
|
1049
|
+
}
|
|
1050
|
+
function applyFire(effect, time) {
|
|
1051
|
+
if (Skia.RuntimeEffect === void 0) return defaultPaint;
|
|
1052
|
+
if (fireRuntimeEffect === null) {
|
|
1053
|
+
const compiled = Skia.RuntimeEffect.Make(FIRE_SKSL);
|
|
1054
|
+
if (compiled === null) return defaultPaint;
|
|
1055
|
+
fireRuntimeEffect = compiled;
|
|
1056
|
+
}
|
|
1057
|
+
if (firePaint === null) firePaint = Skia.Paint();
|
|
1058
|
+
fireUniforms[0] = currentSpriteW;
|
|
1059
|
+
fireUniforms[1] = currentSpriteH;
|
|
1060
|
+
fireUniforms[2] = effect.color.r;
|
|
1061
|
+
fireUniforms[3] = effect.color.g;
|
|
1062
|
+
fireUniforms[4] = effect.color.b;
|
|
1063
|
+
fireUniforms[5] = effect.tipColor.r;
|
|
1064
|
+
fireUniforms[6] = effect.tipColor.g;
|
|
1065
|
+
fireUniforms[7] = effect.tipColor.b;
|
|
1066
|
+
fireUniforms[8] = effect.speed;
|
|
1067
|
+
fireUniforms[9] = effect.intensity;
|
|
1068
|
+
fireUniforms[10] = time;
|
|
1069
|
+
firePaint.setShader(fireRuntimeEffect.makeShader(fireUniforms));
|
|
1070
|
+
return firePaint;
|
|
1071
|
+
}
|
|
1072
|
+
function applyDropShadow(effect) {
|
|
1073
|
+
if (Skia.ImageFilter === void 0) return defaultPaint;
|
|
1074
|
+
if (dropShadowPaint === null) dropShadowPaint = Skia.Paint();
|
|
1075
|
+
writeColor(dropShadowColor, effect.color, 1);
|
|
1076
|
+
dropShadowPaint.setImageFilter(Skia.ImageFilter.MakeDropShadow(effect.offsetX, effect.offsetY, effect.blur, effect.blur, dropShadowColor, null));
|
|
1077
|
+
return dropShadowPaint;
|
|
1078
|
+
}
|
|
1079
|
+
function applyRoofsCity(effect) {
|
|
1080
|
+
if (Skia.RuntimeEffect === void 0) return defaultPaint;
|
|
1081
|
+
if (roofsCityEffect === null) {
|
|
1082
|
+
const compiled = Skia.RuntimeEffect.Make(ROOFS_CITY_SKSL);
|
|
1083
|
+
if (compiled === null) return defaultPaint;
|
|
1084
|
+
roofsCityEffect = compiled;
|
|
1085
|
+
}
|
|
1086
|
+
if (roofsCityPaint === null) roofsCityPaint = Skia.Paint();
|
|
1087
|
+
roofsCityUniforms[0] = effect.resolutionW;
|
|
1088
|
+
roofsCityUniforms[1] = effect.resolutionH;
|
|
1089
|
+
roofsCityUniforms[2] = effect.skyColor.r;
|
|
1090
|
+
roofsCityUniforms[3] = effect.skyColor.g;
|
|
1091
|
+
roofsCityUniforms[4] = effect.skyColor.b;
|
|
1092
|
+
roofsCityUniforms[5] = effect.roofColor.r;
|
|
1093
|
+
roofsCityUniforms[6] = effect.roofColor.g;
|
|
1094
|
+
roofsCityUniforms[7] = effect.roofColor.b;
|
|
1095
|
+
roofsCityUniforms[8] = effect.scrollOffsetY;
|
|
1096
|
+
roofsCityUniforms[9] = effect.density;
|
|
1097
|
+
roofsCityPaint.setShader(roofsCityEffect.makeShader(roofsCityUniforms));
|
|
1098
|
+
return roofsCityPaint;
|
|
1099
|
+
}
|
|
1100
|
+
function applyForestCanopy(effect) {
|
|
1101
|
+
if (Skia.RuntimeEffect === void 0) return defaultPaint;
|
|
1102
|
+
if (forestCanopyEffect === null) {
|
|
1103
|
+
const compiled = Skia.RuntimeEffect.Make(FOREST_CANOPY_SKSL);
|
|
1104
|
+
if (compiled === null) return defaultPaint;
|
|
1105
|
+
forestCanopyEffect = compiled;
|
|
1106
|
+
}
|
|
1107
|
+
if (forestCanopyPaint === null) forestCanopyPaint = Skia.Paint();
|
|
1108
|
+
forestCanopyUniforms[0] = effect.resolutionW;
|
|
1109
|
+
forestCanopyUniforms[1] = effect.resolutionH;
|
|
1110
|
+
forestCanopyUniforms[2] = effect.skyColor.r;
|
|
1111
|
+
forestCanopyUniforms[3] = effect.skyColor.g;
|
|
1112
|
+
forestCanopyUniforms[4] = effect.skyColor.b;
|
|
1113
|
+
forestCanopyUniforms[5] = effect.leafColor.r;
|
|
1114
|
+
forestCanopyUniforms[6] = effect.leafColor.g;
|
|
1115
|
+
forestCanopyUniforms[7] = effect.leafColor.b;
|
|
1116
|
+
forestCanopyUniforms[8] = effect.scrollOffsetY;
|
|
1117
|
+
forestCanopyUniforms[9] = effect.density;
|
|
1118
|
+
forestCanopyPaint.setShader(forestCanopyEffect.makeShader(forestCanopyUniforms));
|
|
1119
|
+
return forestCanopyPaint;
|
|
1120
|
+
}
|
|
1121
|
+
function applyRiverWave(effect) {
|
|
1122
|
+
if (Skia.RuntimeEffect === void 0) return defaultPaint;
|
|
1123
|
+
if (riverWaveEffect === null) {
|
|
1124
|
+
const compiled = Skia.RuntimeEffect.Make(RIVER_WAVE_SKSL);
|
|
1125
|
+
if (compiled === null) return defaultPaint;
|
|
1126
|
+
riverWaveEffect = compiled;
|
|
1127
|
+
}
|
|
1128
|
+
if (riverWavePaint === null) riverWavePaint = Skia.Paint();
|
|
1129
|
+
riverWaveUniforms[0] = effect.resolutionW;
|
|
1130
|
+
riverWaveUniforms[1] = effect.resolutionH;
|
|
1131
|
+
riverWaveUniforms[2] = effect.waterColor.r;
|
|
1132
|
+
riverWaveUniforms[3] = effect.waterColor.g;
|
|
1133
|
+
riverWaveUniforms[4] = effect.waterColor.b;
|
|
1134
|
+
riverWaveUniforms[5] = effect.foamColor.r;
|
|
1135
|
+
riverWaveUniforms[6] = effect.foamColor.g;
|
|
1136
|
+
riverWaveUniforms[7] = effect.foamColor.b;
|
|
1137
|
+
riverWaveUniforms[8] = effect.scrollOffsetY;
|
|
1138
|
+
riverWaveUniforms[9] = effect.waveScale;
|
|
1139
|
+
riverWavePaint.setShader(riverWaveEffect.makeShader(riverWaveUniforms));
|
|
1140
|
+
return riverWavePaint;
|
|
1141
|
+
}
|
|
1142
|
+
function applyMoonCrater(effect) {
|
|
1143
|
+
if (Skia.RuntimeEffect === void 0) return defaultPaint;
|
|
1144
|
+
if (moonCraterEffect === null) {
|
|
1145
|
+
const compiled = Skia.RuntimeEffect.Make(MOON_CRATER_SKSL);
|
|
1146
|
+
if (compiled === null) return defaultPaint;
|
|
1147
|
+
moonCraterEffect = compiled;
|
|
1148
|
+
}
|
|
1149
|
+
if (moonCraterPaint === null) moonCraterPaint = Skia.Paint();
|
|
1150
|
+
moonCraterUniforms[0] = effect.resolutionW;
|
|
1151
|
+
moonCraterUniforms[1] = effect.resolutionH;
|
|
1152
|
+
moonCraterUniforms[2] = effect.moonColor.r;
|
|
1153
|
+
moonCraterUniforms[3] = effect.moonColor.g;
|
|
1154
|
+
moonCraterUniforms[4] = effect.moonColor.b;
|
|
1155
|
+
moonCraterUniforms[5] = effect.craterColor.r;
|
|
1156
|
+
moonCraterUniforms[6] = effect.craterColor.g;
|
|
1157
|
+
moonCraterUniforms[7] = effect.craterColor.b;
|
|
1158
|
+
moonCraterUniforms[8] = effect.scrollOffsetY;
|
|
1159
|
+
moonCraterUniforms[9] = effect.craterCount;
|
|
1160
|
+
moonCraterPaint.setShader(moonCraterEffect.makeShader(moonCraterUniforms));
|
|
1161
|
+
return moonCraterPaint;
|
|
1162
|
+
}
|
|
1163
|
+
function applyCaveInterior(effect) {
|
|
1164
|
+
if (Skia.RuntimeEffect === void 0) return defaultPaint;
|
|
1165
|
+
if (caveInteriorEffect === null) {
|
|
1166
|
+
const compiled = Skia.RuntimeEffect.Make(CAVE_INTERIOR_SKSL);
|
|
1167
|
+
if (compiled === null) return defaultPaint;
|
|
1168
|
+
caveInteriorEffect = compiled;
|
|
1169
|
+
}
|
|
1170
|
+
if (caveInteriorPaint === null) caveInteriorPaint = Skia.Paint();
|
|
1171
|
+
caveInteriorUniforms[0] = effect.resolutionW;
|
|
1172
|
+
caveInteriorUniforms[1] = effect.resolutionH;
|
|
1173
|
+
caveInteriorUniforms[2] = effect.caveColor.r;
|
|
1174
|
+
caveInteriorUniforms[3] = effect.caveColor.g;
|
|
1175
|
+
caveInteriorUniforms[4] = effect.caveColor.b;
|
|
1176
|
+
caveInteriorUniforms[5] = effect.glowColor.r;
|
|
1177
|
+
caveInteriorUniforms[6] = effect.glowColor.g;
|
|
1178
|
+
caveInteriorUniforms[7] = effect.glowColor.b;
|
|
1179
|
+
caveInteriorUniforms[8] = effect.scrollOffsetY;
|
|
1180
|
+
caveInteriorUniforms[9] = effect.stalactiteCount;
|
|
1181
|
+
caveInteriorPaint.setShader(caveInteriorEffect.makeShader(caveInteriorUniforms));
|
|
1182
|
+
return caveInteriorPaint;
|
|
1183
|
+
}
|
|
1184
|
+
function applyMountainHills(effect) {
|
|
1185
|
+
if (Skia.RuntimeEffect === void 0) return defaultPaint;
|
|
1186
|
+
if (mountainHillsEffect === null) {
|
|
1187
|
+
const compiled = Skia.RuntimeEffect.Make(MOUNTAIN_HILLS_SKSL);
|
|
1188
|
+
if (compiled === null) return defaultPaint;
|
|
1189
|
+
mountainHillsEffect = compiled;
|
|
1190
|
+
}
|
|
1191
|
+
if (mountainHillsPaint === null) mountainHillsPaint = Skia.Paint();
|
|
1192
|
+
mountainHillsUniforms[0] = effect.resolutionW;
|
|
1193
|
+
mountainHillsUniforms[1] = effect.resolutionH;
|
|
1194
|
+
mountainHillsUniforms[2] = effect.skyColor.r;
|
|
1195
|
+
mountainHillsUniforms[3] = effect.skyColor.g;
|
|
1196
|
+
mountainHillsUniforms[4] = effect.skyColor.b;
|
|
1197
|
+
mountainHillsUniforms[5] = effect.hillColor.r;
|
|
1198
|
+
mountainHillsUniforms[6] = effect.hillColor.g;
|
|
1199
|
+
mountainHillsUniforms[7] = effect.hillColor.b;
|
|
1200
|
+
mountainHillsUniforms[8] = effect.scrollOffsetY;
|
|
1201
|
+
mountainHillsUniforms[9] = effect.ridgeScale;
|
|
1202
|
+
mountainHillsPaint.setShader(mountainHillsEffect.makeShader(mountainHillsUniforms));
|
|
1203
|
+
return mountainHillsPaint;
|
|
1204
|
+
}
|
|
1205
|
+
function drawOutlined(cvs, image, commands, n, effect, alpha) {
|
|
1206
|
+
const paint = applyOutlinePaint(effect);
|
|
1207
|
+
paint.setAlphaf(alpha);
|
|
1208
|
+
const srcs = getCachedSrcSlice(n);
|
|
1209
|
+
for (let k = 0; k < OUTLINE_OFFSETS.length; k++) {
|
|
1210
|
+
const offset = OUTLINE_OFFSETS[k];
|
|
1211
|
+
fillXforms(commands, n, offset[0] * effect.width, offset[1] * effect.width);
|
|
1212
|
+
const dsts = getCachedXformSlice(n);
|
|
1213
|
+
cvs.drawAtlas(image, srcs, dsts, paint);
|
|
1214
|
+
}
|
|
1215
|
+
paint.setAlphaf(1);
|
|
1216
|
+
defaultPaint.setAlphaf(alpha);
|
|
1217
|
+
fillXforms(commands, n, 0, 0);
|
|
1218
|
+
const mainDsts = getCachedXformSlice(n);
|
|
1219
|
+
cvs.drawAtlas(image, srcs, mainDsts, defaultPaint);
|
|
1220
|
+
defaultPaint.setAlphaf(1);
|
|
1221
|
+
}
|
|
1222
|
+
function resolveEffectPaint(effect) {
|
|
1223
|
+
switch (effect.type) {
|
|
1224
|
+
case "tint": return applyTint(effect);
|
|
1225
|
+
case "flash": return applyFlash(effect);
|
|
1226
|
+
case "grayscale": return applyGrayscale(effect);
|
|
1227
|
+
case "custom": return applyCustom(effect);
|
|
1228
|
+
case "water": return applyWater(effect, currentFrameTime);
|
|
1229
|
+
case "fog": return applyFog(effect, currentFrameTime);
|
|
1230
|
+
case "fire": return applyFire(effect, currentFrameTime);
|
|
1231
|
+
case "drop-shadow": return applyDropShadow(effect);
|
|
1232
|
+
case "outline": return applyOutlinePaint(effect);
|
|
1233
|
+
case "roofs-city": return applyRoofsCity(effect);
|
|
1234
|
+
case "forest-canopy": return applyForestCanopy(effect);
|
|
1235
|
+
case "river-wave": return applyRiverWave(effect);
|
|
1236
|
+
case "moon-crater": return applyMoonCrater(effect);
|
|
1237
|
+
case "cave-interior": return applyCaveInterior(effect);
|
|
1238
|
+
case "mountain-hills": return applyMountainHills(effect);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return {
|
|
1242
|
+
canvas,
|
|
1243
|
+
beginFrame(viewportWidth, viewportHeight, time = 0, pixelDensity = 1) {
|
|
1244
|
+
if (viewportWidth === 0 || viewportHeight === 0) return;
|
|
1245
|
+
_boundRuntime = null;
|
|
1246
|
+
_boundChain = null;
|
|
1247
|
+
currentFrameTime = time;
|
|
1248
|
+
const pd = pixelDensity;
|
|
1249
|
+
recordingBounds.setXYWH(0, 0, viewportWidth * pd, viewportHeight * pd);
|
|
1250
|
+
current = recorder.beginRecording(recordingBounds);
|
|
1251
|
+
if (pd !== 1) current.scale(pd, pd);
|
|
1252
|
+
},
|
|
1253
|
+
drawAtlas(image, commands) {
|
|
1254
|
+
if (current === null) return;
|
|
1255
|
+
const n = commands.length;
|
|
1256
|
+
if (n === 0) return;
|
|
1257
|
+
const effect = commands[0]?.effect;
|
|
1258
|
+
const alpha = commands[0]?.alpha ?? 1;
|
|
1259
|
+
currentSpriteW = commands[0]?.dstW ?? 0;
|
|
1260
|
+
currentSpriteH = commands[0]?.dstH ?? 0;
|
|
1261
|
+
if (effect?.type === "roofs-city" || effect?.type === "forest-canopy" || effect?.type === "river-wave" || effect?.type === "moon-crater" || effect?.type === "cave-interior" || effect?.type === "mountain-hills") {
|
|
1262
|
+
const paint = resolveEffectPaint(effect);
|
|
1263
|
+
paint.setAlphaf(alpha);
|
|
1264
|
+
ensureZoneBgPoolCapacity(n);
|
|
1265
|
+
for (let i = 0; i < n; i++) {
|
|
1266
|
+
const cmd = commands[i];
|
|
1267
|
+
zoneBgRects[i].setXYWH(cmd.dstX, cmd.dstY, cmd.dstW, cmd.dstH);
|
|
1268
|
+
current.drawRect(zoneBgRects[i], paint);
|
|
1269
|
+
}
|
|
1270
|
+
paint.setAlphaf(1);
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
if (commands[0]?.stretch) {
|
|
1274
|
+
ensureStretchPoolCapacity(n);
|
|
1275
|
+
if (stretchPaint === null) stretchPaint = Skia.Paint();
|
|
1276
|
+
const sp = stretchPaint;
|
|
1277
|
+
const sPaint = effect === void 0 || effect.type === "outline" ? sp : resolveEffectPaint(effect);
|
|
1278
|
+
sPaint.setAlphaf(alpha);
|
|
1279
|
+
for (let i = 0; i < n; i++) {
|
|
1280
|
+
const cmd = commands[i];
|
|
1281
|
+
const srcR = stretchSrcRects[i];
|
|
1282
|
+
const dstR = stretchDstRects[i];
|
|
1283
|
+
srcR.setXYWH(cmd.srcX, cmd.srcY, cmd.srcW, cmd.srcH);
|
|
1284
|
+
dstR.setXYWH(cmd.dstX, cmd.dstY, cmd.dstW, cmd.dstH);
|
|
1285
|
+
current.drawImageRect(image, srcR, dstR, sPaint);
|
|
1286
|
+
}
|
|
1287
|
+
sPaint.setAlphaf(1);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
ensurePoolCapacity(n);
|
|
1291
|
+
fillSrcRects(commands, n);
|
|
1292
|
+
if (effect?.type === "outline") {
|
|
1293
|
+
drawOutlined(current, image, commands, n, effect, alpha);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
fillXforms(commands, n, 0, 0);
|
|
1297
|
+
const srcs = getCachedSrcSlice(n);
|
|
1298
|
+
const dsts = getCachedXformSlice(n);
|
|
1299
|
+
const paint = effect === void 0 ? defaultPaint : resolveEffectPaint(effect);
|
|
1300
|
+
paint.setAlphaf(alpha);
|
|
1301
|
+
current.drawAtlas(image, srcs, dsts, paint);
|
|
1302
|
+
paint.setAlphaf(1);
|
|
1303
|
+
},
|
|
1304
|
+
setPostFxBinding(runtime, chain) {
|
|
1305
|
+
_boundRuntime = runtime;
|
|
1306
|
+
_boundChain = chain;
|
|
1307
|
+
},
|
|
1308
|
+
hasPostFxBinding() {
|
|
1309
|
+
return _boundRuntime !== null && !!_boundRuntime.supported && _boundRuntime.buildPass !== void 0 && _boundChain !== null;
|
|
1310
|
+
},
|
|
1311
|
+
buildPostFxPasses(runtime, chain, width, height) {
|
|
1312
|
+
const rt = runtime ?? _boundRuntime;
|
|
1313
|
+
const ch = chain ?? _boundChain;
|
|
1314
|
+
if (rt === null || !rt.supported) return [];
|
|
1315
|
+
if (rt.buildPass === void 0) {
|
|
1316
|
+
logOnce("buildPostFxPasses: runtime.supported but buildPass absent — no passes");
|
|
1317
|
+
return [];
|
|
1318
|
+
}
|
|
1319
|
+
if (ch === null) return [];
|
|
1320
|
+
const passes = rt.buildPass(ch, width, height);
|
|
1321
|
+
if (passes.length === 0) return [];
|
|
1322
|
+
const out = [];
|
|
1323
|
+
for (const pass of passes) {
|
|
1324
|
+
const shader = pass.shader;
|
|
1325
|
+
const uniformCount = typeof shader.getUniformCount === "function" ? shader.getUniformCount() : 0;
|
|
1326
|
+
const uniforms = {};
|
|
1327
|
+
for (let i = 0; i < uniformCount; i++) {
|
|
1328
|
+
const name = typeof shader.getUniformName === "function" ? shader.getUniformName(i) : String(i);
|
|
1329
|
+
const info = typeof shader.getUniform === "function" ? shader.getUniform(i) : {
|
|
1330
|
+
columns: 1,
|
|
1331
|
+
rows: 1,
|
|
1332
|
+
slot: i
|
|
1333
|
+
};
|
|
1334
|
+
const size = info.columns * info.rows;
|
|
1335
|
+
if (size === 1) uniforms[name] = pass.uniforms[info.slot] ?? 0;
|
|
1336
|
+
else {
|
|
1337
|
+
const vec = [];
|
|
1338
|
+
for (let k = 0; k < size; k++) vec.push(pass.uniforms[info.slot + k] ?? 0);
|
|
1339
|
+
uniforms[name] = vec;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
out.push({
|
|
1343
|
+
id: pass.id,
|
|
1344
|
+
source: shader,
|
|
1345
|
+
uniforms,
|
|
1346
|
+
children: pass.children
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
return out;
|
|
1350
|
+
},
|
|
1351
|
+
endFrame() {
|
|
1352
|
+
if (current === null) return null;
|
|
1353
|
+
current = null;
|
|
1354
|
+
return recorder.finishRecordingAsPicture();
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
}
|
|
1358
|
+
/** Zero both branch counters unconditionally (no __DEV__ guard). */
|
|
1359
|
+
function resetXformCounters() {
|
|
1360
|
+
_xformUnrotatedCount = 0;
|
|
1361
|
+
_xformRotatedCount = 0;
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Cumulative `fillXforms` branch hit counts since the last reset. Both stay 0
|
|
1365
|
+
* when `__DEV__` is falsy or absent.
|
|
1366
|
+
*
|
|
1367
|
+
* These count transform WRITES, not sprites: a run drawn with an outline effect
|
|
1368
|
+
* passes through `fillXforms` 9 times (8 offset passes + 1 main pass), so it
|
|
1369
|
+
* contributes 9 hits per command — the transform work the renderer actually
|
|
1370
|
+
* performs. The counters are process-global: every `createFrameRenderer`
|
|
1371
|
+
* instance increments the same pair, so readings sum across live renderers.
|
|
1372
|
+
*
|
|
1373
|
+
* Allocates one object per call — never call it from the render loop.
|
|
1374
|
+
*/
|
|
1375
|
+
function getXformCounters() {
|
|
1376
|
+
return {
|
|
1377
|
+
rotated: _xformRotatedCount,
|
|
1378
|
+
unrotated: _xformUnrotatedCount
|
|
1379
|
+
};
|
|
1561
1380
|
}
|
|
1562
1381
|
function writeColor(out, color, alphaMul) {
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1382
|
+
out[0] = color.r;
|
|
1383
|
+
out[1] = color.g;
|
|
1384
|
+
out[2] = color.b;
|
|
1385
|
+
out[3] = color.a * alphaMul;
|
|
1567
1386
|
}
|
|
1568
1387
|
function writeGrayscaleMatrix(out, amount) {
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1388
|
+
const inv = 1 - amount;
|
|
1389
|
+
const lr = .299 * amount;
|
|
1390
|
+
const lg = .587 * amount;
|
|
1391
|
+
const lb = .114 * amount;
|
|
1392
|
+
out[0] = inv + lr;
|
|
1393
|
+
out[1] = lg;
|
|
1394
|
+
out[2] = lb;
|
|
1395
|
+
out[3] = 0;
|
|
1396
|
+
out[4] = 0;
|
|
1397
|
+
out[5] = lr;
|
|
1398
|
+
out[6] = inv + lg;
|
|
1399
|
+
out[7] = lb;
|
|
1400
|
+
out[8] = 0;
|
|
1401
|
+
out[9] = 0;
|
|
1402
|
+
out[10] = lr;
|
|
1403
|
+
out[11] = lg;
|
|
1404
|
+
out[12] = inv + lb;
|
|
1405
|
+
out[13] = 0;
|
|
1406
|
+
out[14] = 0;
|
|
1407
|
+
out[15] = 0;
|
|
1408
|
+
out[16] = 0;
|
|
1409
|
+
out[17] = 0;
|
|
1410
|
+
out[18] = 1;
|
|
1411
|
+
out[19] = 0;
|
|
1593
1412
|
}
|
|
1594
1413
|
function fillCustomUniforms(effect, shader, out) {
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1414
|
+
const count = shader.getUniformCount();
|
|
1415
|
+
for (let i = 0; i < out.length; i++) out[i] = 0;
|
|
1416
|
+
for (let i = 0; i < count; i++) {
|
|
1417
|
+
const info = shader.getUniform(i);
|
|
1418
|
+
const name = shader.getUniformName(i);
|
|
1419
|
+
const value = effect.uniforms[name];
|
|
1420
|
+
const size = info.columns * info.rows;
|
|
1421
|
+
if (value === void 0) continue;
|
|
1422
|
+
if (typeof value === "number") {
|
|
1423
|
+
out[info.slot] = value;
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
for (let k = 0; k < size; k++) out[info.slot + k] = value[k] ?? 0;
|
|
1427
|
+
}
|
|
1611
1428
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1429
|
+
//#endregion
|
|
1430
|
+
//#region src/render-system.ts
|
|
1431
|
+
/**
|
|
1432
|
+
* Create a render system function.
|
|
1433
|
+
*
|
|
1434
|
+
* This is not a defineSystem() wrapper because it needs extra parameters
|
|
1435
|
+
* (SpriteBatch) beyond what the ECS system interface provides. Instead,
|
|
1436
|
+
* it returns a function to call in your game loop.
|
|
1437
|
+
*
|
|
1438
|
+
* The returned function only **queues** sprite draws into `batch` — it
|
|
1439
|
+
* does not call `batch.flush()`. Flushing is owned by the frame owner
|
|
1440
|
+
* (e.g. `<GameView>`) so that all draw sources (ECS, particles, custom)
|
|
1441
|
+
* can contribute to a single flush at the render phase.
|
|
1442
|
+
*
|
|
1443
|
+
* @param world The ECS world.
|
|
1444
|
+
* @param query Query that matches entities with Position + Sprite.
|
|
1445
|
+
* @param positionDef ComponentDef for Position.
|
|
1446
|
+
* @param spriteDef ComponentDef for Sprite.
|
|
1447
|
+
* @param batch SpriteBatch instance to queue draws into.
|
|
1448
|
+
*/
|
|
1449
|
+
function createRenderSystem(world, query, positionDef, spriteDef, batch) {
|
|
1450
|
+
const entries = [];
|
|
1451
|
+
return () => {
|
|
1452
|
+
let i = 0;
|
|
1453
|
+
for (const entityId of query) {
|
|
1454
|
+
const sprite = world.get(entityId, spriteDef);
|
|
1455
|
+
if (!sprite) continue;
|
|
1456
|
+
if (i >= entries.length) entries.push({
|
|
1457
|
+
entityId,
|
|
1458
|
+
z: 0
|
|
1459
|
+
});
|
|
1460
|
+
const entry = entries[i];
|
|
1461
|
+
entry.entityId = entityId;
|
|
1462
|
+
entry.z = sprite.z;
|
|
1463
|
+
i++;
|
|
1464
|
+
}
|
|
1465
|
+
const count = i;
|
|
1466
|
+
if (entries.length > count * 2 && entries.length > 64) entries.length = count;
|
|
1467
|
+
sortByDrawOrder(entries, count);
|
|
1468
|
+
for (let j = 0; j < count; j++) {
|
|
1469
|
+
const entry = entries[j];
|
|
1470
|
+
const pos = world.get(entry.entityId, positionDef);
|
|
1471
|
+
const sprite = world.get(entry.entityId, spriteDef);
|
|
1472
|
+
if (!pos || !sprite) continue;
|
|
1473
|
+
const anchor = sprite.anchor;
|
|
1474
|
+
batch.draw(sprite.image, sprite.frame, pos.x, pos.y, sprite.width, sprite.height, sprite.effect, anchor ? anchor.x : 0, anchor ? anchor.y : 0, sprite.alpha ?? 1, sprite.rotation ?? 0, sprite.stretch ?? false);
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
/** Canonical Sprite component — queried by the render system alongside `Position`. */
|
|
1479
|
+
const Sprite = (0, _flare_engine_ecs.defineComponent)("Sprite", () => ({
|
|
1480
|
+
image: null,
|
|
1481
|
+
frame: {
|
|
1482
|
+
name: "",
|
|
1483
|
+
x: 0,
|
|
1484
|
+
y: 0,
|
|
1485
|
+
w: 0,
|
|
1486
|
+
h: 0
|
|
1487
|
+
},
|
|
1488
|
+
z: 0
|
|
1489
|
+
}));
|
|
1490
|
+
//#endregion
|
|
1491
|
+
//#region src/renderer.ts
|
|
1492
|
+
/**
|
|
1493
|
+
* Renderer — manages the Skia Canvas lifecycle for a frame.
|
|
1494
|
+
* `begin()` to start, draw your content, `end()` to finish.
|
|
1495
|
+
*/
|
|
1496
|
+
var Renderer = class {
|
|
1497
|
+
/** Current frame stats. */
|
|
1498
|
+
stats = {
|
|
1499
|
+
drawCalls: 0,
|
|
1500
|
+
batchCount: 0
|
|
1501
|
+
};
|
|
1502
|
+
_canvas = null;
|
|
1503
|
+
_inFrame = false;
|
|
1504
|
+
/** Set the canvas to render to. Called once when canvas is available. */
|
|
1505
|
+
setCanvas(canvas) {
|
|
1506
|
+
this._canvas = canvas;
|
|
1507
|
+
}
|
|
1508
|
+
/** Get the current canvas. Throws if not in a frame. */
|
|
1509
|
+
get canvas() {
|
|
1510
|
+
if (!this._canvas) throw new Error("Renderer: no canvas set");
|
|
1511
|
+
return this._canvas;
|
|
1512
|
+
}
|
|
1513
|
+
/** Whether we are currently between begin() and end(). */
|
|
1514
|
+
get inFrame() {
|
|
1515
|
+
return this._inFrame;
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* Begin a new frame. Clears the canvas and resets stats.
|
|
1519
|
+
* @param clearColor 32-bit ARGB color to clear with (default black = 0xFF000000).
|
|
1520
|
+
*/
|
|
1521
|
+
begin(clearColor = 4278190080) {
|
|
1522
|
+
if (this._inFrame) throw new Error("Renderer: begin() called while already in frame");
|
|
1523
|
+
this._inFrame = true;
|
|
1524
|
+
this.stats.drawCalls = 0;
|
|
1525
|
+
this.stats.batchCount = 0;
|
|
1526
|
+
this.canvas.save();
|
|
1527
|
+
this.canvas.clear(clearColor);
|
|
1528
|
+
}
|
|
1529
|
+
/**
|
|
1530
|
+
* End the current frame. Restores canvas state.
|
|
1531
|
+
*/
|
|
1532
|
+
end() {
|
|
1533
|
+
if (!this._inFrame) throw new Error("Renderer: end() called without begin()");
|
|
1534
|
+
this.canvas.restore();
|
|
1535
|
+
this._inFrame = false;
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* Apply camera transform to the canvas.
|
|
1539
|
+
* Call after begin() and before drawing.
|
|
1540
|
+
*/
|
|
1541
|
+
applyCamera(cameraX, cameraY, zoom, rotation, viewportWidth, viewportHeight) {
|
|
1542
|
+
const c = this.canvas;
|
|
1543
|
+
c.translate(viewportWidth / 2, viewportHeight / 2);
|
|
1544
|
+
c.scale(zoom, zoom);
|
|
1545
|
+
if (rotation !== 0) c.rotate(-rotation * 180 / Math.PI, 0, 0);
|
|
1546
|
+
c.translate(-cameraX, -cameraY);
|
|
1547
|
+
}
|
|
1548
|
+
/** Increment draw call counter. Invoked in tandem with recordBatch, once per
|
|
1549
|
+
* flush run in SpriteBatch.flush. */
|
|
1550
|
+
recordDrawCall() {
|
|
1551
|
+
this.stats.drawCalls++;
|
|
1552
|
+
}
|
|
1553
|
+
/** Increment batch counter. Invoked in tandem with recordDrawCall, once per
|
|
1554
|
+
* flush run in SpriteBatch.flush. */
|
|
1555
|
+
recordBatch() {
|
|
1556
|
+
this.stats.batchCount++;
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
//#endregion
|
|
1560
|
+
//#region src/sprite-batch.ts
|
|
1561
|
+
/**
|
|
1562
|
+
* SpriteBatch — collects sprite draw commands and groups them by texture
|
|
1563
|
+
* (and optionally by effect) for efficient batched rendering.
|
|
1564
|
+
*
|
|
1565
|
+
* Usage:
|
|
1566
|
+
* 1. Call `draw()` for each sprite, optionally passing an effect
|
|
1567
|
+
* 2. Call `flush()` with an atlas draw callback to execute all batched draws
|
|
1568
|
+
* 3. Batching groups consecutive commands by `(image, effect, alpha, stretch)`
|
|
1569
|
+
* identity, preserving global insertion (z-sorted) order. Sharing an effect
|
|
1570
|
+
* reference and the same alpha across consecutive entities maximizes batch
|
|
1571
|
+
* size while keeping sprites in correct draw order.
|
|
1572
|
+
*
|
|
1573
|
+
* The actual Skia Atlas draw call is delegated to a callback so this module
|
|
1574
|
+
* remains testable without Skia.
|
|
1575
|
+
*/
|
|
1576
|
+
var SpriteBatch = class {
|
|
1577
|
+
_commands = [];
|
|
1578
|
+
_pool = [];
|
|
1579
|
+
_batches = [];
|
|
1580
|
+
_allocCount = 0;
|
|
1581
|
+
/** Number of queued draw commands. */
|
|
1582
|
+
get count() {
|
|
1583
|
+
return this._commands.length;
|
|
1584
|
+
}
|
|
1585
|
+
/** Cumulative number of SpriteDrawCommand objects allocated because the internal
|
|
1586
|
+
* command pool was empty. Steady state should be 0 after warm-up. */
|
|
1587
|
+
get allocCount() {
|
|
1588
|
+
return this._allocCount;
|
|
1589
|
+
}
|
|
1590
|
+
/** Queue a sprite draw from a frame.
|
|
1591
|
+
*
|
|
1592
|
+
* `anchorX` / `anchorY` define the pivot point of the sprite as a
|
|
1593
|
+
* fraction of its destination size — `(0, 0)` = top-left (default),
|
|
1594
|
+
* `(0.5, 0.5)` = center, `(1, 1)` = bottom-right. `dstX` / `dstY`
|
|
1595
|
+
* then refer to the anchor point instead of the top-left corner.
|
|
1596
|
+
*
|
|
1597
|
+
* `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */
|
|
1598
|
+
draw(image, frame, dstX, dstY, dstW, dstH, effect, anchorX = 0, anchorY = 0, alpha = 1, rotation = 0, stretch = false) {
|
|
1599
|
+
const cmd = this._acquire();
|
|
1600
|
+
const w = dstW ?? frame.w;
|
|
1601
|
+
const h = dstH ?? frame.h;
|
|
1602
|
+
cmd.image = image;
|
|
1603
|
+
cmd.srcX = frame.x;
|
|
1604
|
+
cmd.srcY = frame.y;
|
|
1605
|
+
cmd.srcW = frame.w;
|
|
1606
|
+
cmd.srcH = frame.h;
|
|
1607
|
+
cmd.dstX = dstX - anchorX * w;
|
|
1608
|
+
cmd.dstY = dstY - anchorY * h;
|
|
1609
|
+
cmd.dstW = w;
|
|
1610
|
+
cmd.dstH = h;
|
|
1611
|
+
cmd.effect = effect;
|
|
1612
|
+
cmd.alpha = alpha;
|
|
1613
|
+
cmd.rotation = rotation;
|
|
1614
|
+
cmd.anchorX = anchorX;
|
|
1615
|
+
cmd.anchorY = anchorY;
|
|
1616
|
+
cmd.stretch = stretch;
|
|
1617
|
+
this._commands.push(cmd);
|
|
1618
|
+
}
|
|
1619
|
+
/** Queue a raw sprite draw with explicit src/dst rects. `dstX` / `dstY`
|
|
1620
|
+
* refer to the anchor point — offset by `anchorX * dstW` / `anchorY * dstH`.
|
|
1621
|
+
*
|
|
1622
|
+
* `alpha` is a per-instance opacity multiplier in [0, 1]. Default 1. */
|
|
1623
|
+
drawRaw(image, srcX, srcY, srcW, srcH, dstX, dstY, dstW, dstH, effect, anchorX = 0, anchorY = 0, alpha = 1, rotation = 0, stretch = false) {
|
|
1624
|
+
const cmd = this._acquire();
|
|
1625
|
+
cmd.image = image;
|
|
1626
|
+
cmd.srcX = srcX;
|
|
1627
|
+
cmd.srcY = srcY;
|
|
1628
|
+
cmd.srcW = srcW;
|
|
1629
|
+
cmd.srcH = srcH;
|
|
1630
|
+
cmd.dstX = dstX - anchorX * dstW;
|
|
1631
|
+
cmd.dstY = dstY - anchorY * dstH;
|
|
1632
|
+
cmd.dstW = dstW;
|
|
1633
|
+
cmd.dstH = dstH;
|
|
1634
|
+
cmd.effect = effect;
|
|
1635
|
+
cmd.alpha = alpha;
|
|
1636
|
+
cmd.rotation = rotation;
|
|
1637
|
+
cmd.anchorX = anchorX;
|
|
1638
|
+
cmd.anchorY = anchorY;
|
|
1639
|
+
cmd.stretch = stretch;
|
|
1640
|
+
this._commands.push(cmd);
|
|
1641
|
+
}
|
|
1642
|
+
/**
|
|
1643
|
+
* Flush all queued commands in insertion order, grouping maximal consecutive
|
|
1644
|
+
* runs that share the same `(image, effect, alpha, stretch)` key into a
|
|
1645
|
+
* single `drawAtlas` call. This preserves global z-order (the caller is
|
|
1646
|
+
* responsible for queuing in z-sorted order) while still batching runs of
|
|
1647
|
+
* same-key sprites for efficiency.
|
|
1648
|
+
*
|
|
1649
|
+
* @param drawAtlas Callback invoked once per run. Receives
|
|
1650
|
+
* `(image, commands)` — all commands in a run share the same key and are
|
|
1651
|
+
* contiguous in the original insertion order.
|
|
1652
|
+
* @param renderer Optional renderer to record stats.
|
|
1653
|
+
*/
|
|
1654
|
+
flush(drawAtlas, renderer) {
|
|
1655
|
+
if (this._commands.length === 0) return;
|
|
1656
|
+
let run = this._batches[0];
|
|
1657
|
+
if (run === void 0) {
|
|
1658
|
+
run = {
|
|
1659
|
+
image: null,
|
|
1660
|
+
effect: void 0,
|
|
1661
|
+
alpha: 1,
|
|
1662
|
+
stretch: false,
|
|
1663
|
+
commands: []
|
|
1664
|
+
};
|
|
1665
|
+
this._batches[0] = run;
|
|
1666
|
+
}
|
|
1667
|
+
for (const cmd of this._commands) {
|
|
1668
|
+
if (run.commands.length > 0 && (cmd.image !== run.image || cmd.effect !== run.effect || cmd.alpha !== run.alpha || cmd.stretch !== run.stretch)) {
|
|
1669
|
+
drawAtlas(run.image, run.commands);
|
|
1670
|
+
if (renderer) {
|
|
1671
|
+
renderer.recordDrawCall();
|
|
1672
|
+
renderer.recordBatch();
|
|
1673
|
+
}
|
|
1674
|
+
run.commands.length = 0;
|
|
1675
|
+
}
|
|
1676
|
+
run.image = cmd.image;
|
|
1677
|
+
run.effect = cmd.effect;
|
|
1678
|
+
run.alpha = cmd.alpha;
|
|
1679
|
+
run.stretch = cmd.stretch;
|
|
1680
|
+
run.commands.push(cmd);
|
|
1681
|
+
}
|
|
1682
|
+
if (run.commands.length > 0) {
|
|
1683
|
+
drawAtlas(run.image, run.commands);
|
|
1684
|
+
if (renderer) {
|
|
1685
|
+
renderer.recordDrawCall();
|
|
1686
|
+
renderer.recordBatch();
|
|
1687
|
+
}
|
|
1688
|
+
run.commands.length = 0;
|
|
1689
|
+
}
|
|
1690
|
+
for (const cmd of this._commands) this._pool.push(cmd);
|
|
1691
|
+
this._commands.length = 0;
|
|
1692
|
+
}
|
|
1693
|
+
/** Clear all queued commands without drawing. */
|
|
1694
|
+
clear() {
|
|
1695
|
+
for (const cmd of this._commands) this._pool.push(cmd);
|
|
1696
|
+
this._commands.length = 0;
|
|
1697
|
+
}
|
|
1698
|
+
_acquire() {
|
|
1699
|
+
if (this._pool.length > 0) return this._pool.pop();
|
|
1700
|
+
this._allocCount++;
|
|
1701
|
+
return {
|
|
1702
|
+
image: null,
|
|
1703
|
+
srcX: 0,
|
|
1704
|
+
srcY: 0,
|
|
1705
|
+
srcW: 0,
|
|
1706
|
+
srcH: 0,
|
|
1707
|
+
dstX: 0,
|
|
1708
|
+
dstY: 0,
|
|
1709
|
+
dstW: 0,
|
|
1710
|
+
dstH: 0,
|
|
1711
|
+
effect: void 0,
|
|
1712
|
+
alpha: 1,
|
|
1713
|
+
rotation: 0,
|
|
1714
|
+
anchorX: 0,
|
|
1715
|
+
anchorY: 0,
|
|
1716
|
+
stretch: false
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
//#endregion
|
|
1721
|
+
//#region src/sprite-font.ts
|
|
1722
|
+
/**
|
|
1723
|
+
* Parse BMFont JSON format (common output from tools like Hiero, BMFont, msdf-bmfont).
|
|
1724
|
+
*/
|
|
1614
1725
|
function parseBMFontJSON(json) {
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
return {
|
|
1636
|
-
lineHeight: data.common.lineHeight,
|
|
1637
|
-
base: data.common.base,
|
|
1638
|
-
glyphs,
|
|
1639
|
-
kernings
|
|
1640
|
-
};
|
|
1726
|
+
const data = json;
|
|
1727
|
+
const glyphs = /* @__PURE__ */ new Map();
|
|
1728
|
+
for (const c of data.chars) glyphs.set(c.id, {
|
|
1729
|
+
id: c.id,
|
|
1730
|
+
x: c.x,
|
|
1731
|
+
y: c.y,
|
|
1732
|
+
width: c.width,
|
|
1733
|
+
height: c.height,
|
|
1734
|
+
xOffset: c.xoffset,
|
|
1735
|
+
yOffset: c.yoffset,
|
|
1736
|
+
xAdvance: c.xadvance
|
|
1737
|
+
});
|
|
1738
|
+
const kernings = /* @__PURE__ */ new Map();
|
|
1739
|
+
if (data.kernings) for (const k of data.kernings) kernings.set(k.first << 16 | k.second, k.amount);
|
|
1740
|
+
return {
|
|
1741
|
+
lineHeight: data.common.lineHeight,
|
|
1742
|
+
base: data.common.base,
|
|
1743
|
+
glyphs,
|
|
1744
|
+
kernings
|
|
1745
|
+
};
|
|
1641
1746
|
}
|
|
1747
|
+
/** Get kerning between two characters. */
|
|
1642
1748
|
function getKerning(font, first, second) {
|
|
1643
|
-
|
|
1749
|
+
return font.kernings.get(first << 16 | second) ?? 0;
|
|
1644
1750
|
}
|
|
1751
|
+
/** Measure the width of a text string in pixels. */
|
|
1645
1752
|
function measureText(font, text) {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
}
|
|
1658
|
-
return width;
|
|
1753
|
+
let width = 0;
|
|
1754
|
+
let prevCode = -1;
|
|
1755
|
+
for (let i = 0; i < text.length; i++) {
|
|
1756
|
+
const code = text.charCodeAt(i);
|
|
1757
|
+
const glyph = font.glyphs.get(code);
|
|
1758
|
+
if (!glyph) continue;
|
|
1759
|
+
if (prevCode >= 0) width += getKerning(font, prevCode, code);
|
|
1760
|
+
width += glyph.xAdvance;
|
|
1761
|
+
prevCode = code;
|
|
1762
|
+
}
|
|
1763
|
+
return width;
|
|
1659
1764
|
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Draw text using a SpriteBatch.
|
|
1767
|
+
* Characters are batched as individual sprites from the font texture.
|
|
1768
|
+
*/
|
|
1660
1769
|
function drawText(batch, font, image, text, x, y, scale = 1) {
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
glyph.x,
|
|
1673
|
-
glyph.y,
|
|
1674
|
-
glyph.width,
|
|
1675
|
-
glyph.height,
|
|
1676
|
-
cursorX + glyph.xOffset * scale,
|
|
1677
|
-
y + glyph.yOffset * scale,
|
|
1678
|
-
glyph.width * scale,
|
|
1679
|
-
glyph.height * scale
|
|
1680
|
-
);
|
|
1681
|
-
cursorX += glyph.xAdvance * scale;
|
|
1682
|
-
prevCode = code;
|
|
1683
|
-
}
|
|
1770
|
+
let cursorX = x;
|
|
1771
|
+
let prevCode = -1;
|
|
1772
|
+
for (let i = 0; i < text.length; i++) {
|
|
1773
|
+
const code = text.charCodeAt(i);
|
|
1774
|
+
const glyph = font.glyphs.get(code);
|
|
1775
|
+
if (!glyph) continue;
|
|
1776
|
+
if (prevCode >= 0) cursorX += getKerning(font, prevCode, code) * scale;
|
|
1777
|
+
batch.drawRaw(image, glyph.x, glyph.y, glyph.width, glyph.height, cursorX + glyph.xOffset * scale, y + glyph.yOffset * scale, glyph.width * scale, glyph.height * scale);
|
|
1778
|
+
cursorX += glyph.xAdvance * scale;
|
|
1779
|
+
prevCode = code;
|
|
1780
|
+
}
|
|
1684
1781
|
}
|
|
1782
|
+
//#endregion
|
|
1783
|
+
exports.CAVE_INTERIOR_SKSL = CAVE_INTERIOR_SKSL;
|
|
1784
|
+
exports.Camera2D = Camera2D;
|
|
1785
|
+
exports.FIRE_SKSL = FIRE_SKSL;
|
|
1786
|
+
exports.FOG_SKSL = FOG_SKSL;
|
|
1787
|
+
exports.FOREST_CANOPY_SKSL = FOREST_CANOPY_SKSL;
|
|
1788
|
+
exports.MOON_CRATER_SKSL = MOON_CRATER_SKSL;
|
|
1789
|
+
exports.MOUNTAIN_HILLS_SKSL = MOUNTAIN_HILLS_SKSL;
|
|
1790
|
+
exports.RIVER_WAVE_SKSL = RIVER_WAVE_SKSL;
|
|
1791
|
+
exports.ROOFS_CITY_SKSL = ROOFS_CITY_SKSL;
|
|
1792
|
+
exports.Renderer = Renderer;
|
|
1793
|
+
exports.Sprite = Sprite;
|
|
1794
|
+
exports.SpriteBatch = SpriteBatch;
|
|
1795
|
+
exports.WATER_SKSL = WATER_SKSL;
|
|
1796
|
+
exports.caveInterior = caveInterior;
|
|
1797
|
+
exports.createFrameRenderer = createFrameRenderer;
|
|
1798
|
+
exports.createRenderSystem = createRenderSystem;
|
|
1799
|
+
exports.custom = custom;
|
|
1800
|
+
exports.drawOrderCompare = drawOrderCompare;
|
|
1801
|
+
exports.drawText = drawText;
|
|
1802
|
+
exports.dropShadow = dropShadow;
|
|
1803
|
+
exports.fire = fire;
|
|
1804
|
+
exports.flash = flash;
|
|
1805
|
+
exports.flashIntensity = flashIntensity;
|
|
1806
|
+
exports.fog = fog;
|
|
1807
|
+
exports.forestCanopy = forestCanopy;
|
|
1808
|
+
exports.getKerning = getKerning;
|
|
1809
|
+
exports.getXformCounters = getXformCounters;
|
|
1810
|
+
exports.grayscale = grayscale;
|
|
1811
|
+
exports.measureText = measureText;
|
|
1812
|
+
exports.moonCrater = moonCrater;
|
|
1813
|
+
exports.mountainHills = mountainHills;
|
|
1814
|
+
exports.outline = outline;
|
|
1815
|
+
exports.parseBMFontJSON = parseBMFontJSON;
|
|
1816
|
+
exports.resetXformCounters = resetXformCounters;
|
|
1817
|
+
exports.riverWave = riverWave;
|
|
1818
|
+
exports.roofsCity = roofsCity;
|
|
1819
|
+
exports.sortByDrawOrder = sortByDrawOrder;
|
|
1820
|
+
exports.tint = tint;
|
|
1821
|
+
exports.updateEffect = updateEffect;
|
|
1822
|
+
exports.water = water;
|
|
1685
1823
|
|
|
1686
|
-
// src/render-system.ts
|
|
1687
|
-
var import_ecs = require("@flare-engine/ecs");
|
|
1688
|
-
function createRenderSystem(world, query, positionDef, spriteDef, batch) {
|
|
1689
|
-
const entries = [];
|
|
1690
|
-
return () => {
|
|
1691
|
-
let i = 0;
|
|
1692
|
-
for (const entityId of query) {
|
|
1693
|
-
const sprite = world.get(entityId, spriteDef);
|
|
1694
|
-
if (!sprite) continue;
|
|
1695
|
-
if (i >= entries.length) {
|
|
1696
|
-
entries.push({ entityId, z: 0 });
|
|
1697
|
-
}
|
|
1698
|
-
const entry = entries[i];
|
|
1699
|
-
entry.entityId = entityId;
|
|
1700
|
-
entry.z = sprite.z;
|
|
1701
|
-
i++;
|
|
1702
|
-
}
|
|
1703
|
-
const count = i;
|
|
1704
|
-
if (entries.length > count * 2 && entries.length > 64) {
|
|
1705
|
-
entries.length = count;
|
|
1706
|
-
}
|
|
1707
|
-
sortByDrawOrder(entries, count);
|
|
1708
|
-
for (let j = 0; j < count; j++) {
|
|
1709
|
-
const entry = entries[j];
|
|
1710
|
-
const pos = world.get(entry.entityId, positionDef);
|
|
1711
|
-
const sprite = world.get(entry.entityId, spriteDef);
|
|
1712
|
-
if (!pos || !sprite) continue;
|
|
1713
|
-
const anchor = sprite.anchor;
|
|
1714
|
-
batch.draw(
|
|
1715
|
-
sprite.image,
|
|
1716
|
-
sprite.frame,
|
|
1717
|
-
pos.x,
|
|
1718
|
-
pos.y,
|
|
1719
|
-
sprite.width,
|
|
1720
|
-
sprite.height,
|
|
1721
|
-
sprite.effect,
|
|
1722
|
-
anchor ? anchor.x : 0,
|
|
1723
|
-
anchor ? anchor.y : 0,
|
|
1724
|
-
sprite.alpha ?? 1,
|
|
1725
|
-
sprite.rotation ?? 0,
|
|
1726
|
-
sprite.stretch ?? false
|
|
1727
|
-
);
|
|
1728
|
-
}
|
|
1729
|
-
};
|
|
1730
|
-
}
|
|
1731
|
-
var Sprite = (0, import_ecs.defineComponent)("Sprite", () => ({
|
|
1732
|
-
image: null,
|
|
1733
|
-
frame: { name: "", x: 0, y: 0, w: 0, h: 0 },
|
|
1734
|
-
z: 0
|
|
1735
|
-
}));
|
|
1736
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1737
|
-
0 && (module.exports = {
|
|
1738
|
-
CAVE_INTERIOR_SKSL,
|
|
1739
|
-
Camera2D,
|
|
1740
|
-
FIRE_SKSL,
|
|
1741
|
-
FOG_SKSL,
|
|
1742
|
-
FOREST_CANOPY_SKSL,
|
|
1743
|
-
MOON_CRATER_SKSL,
|
|
1744
|
-
MOUNTAIN_HILLS_SKSL,
|
|
1745
|
-
RIVER_WAVE_SKSL,
|
|
1746
|
-
ROOFS_CITY_SKSL,
|
|
1747
|
-
Renderer,
|
|
1748
|
-
Sprite,
|
|
1749
|
-
SpriteBatch,
|
|
1750
|
-
WATER_SKSL,
|
|
1751
|
-
caveInterior,
|
|
1752
|
-
createFrameRenderer,
|
|
1753
|
-
createRenderSystem,
|
|
1754
|
-
custom,
|
|
1755
|
-
drawOrderCompare,
|
|
1756
|
-
drawText,
|
|
1757
|
-
dropShadow,
|
|
1758
|
-
fire,
|
|
1759
|
-
flash,
|
|
1760
|
-
flashIntensity,
|
|
1761
|
-
fog,
|
|
1762
|
-
forestCanopy,
|
|
1763
|
-
getKerning,
|
|
1764
|
-
grayscale,
|
|
1765
|
-
measureText,
|
|
1766
|
-
moonCrater,
|
|
1767
|
-
mountainHills,
|
|
1768
|
-
outline,
|
|
1769
|
-
parseBMFontJSON,
|
|
1770
|
-
riverWave,
|
|
1771
|
-
roofsCity,
|
|
1772
|
-
sortByDrawOrder,
|
|
1773
|
-
tint,
|
|
1774
|
-
updateEffect,
|
|
1775
|
-
water
|
|
1776
|
-
});
|
|
1777
1824
|
//# sourceMappingURL=index.js.map
|