webwallgl 1.0.0-beta1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +11 -8
- package/README.md +11 -8
- package/package.json +7 -1
- package/webwallgl.global.js +949 -117
- package/webwallgl.global.js.map +1 -1
- package/webwallgl.global.min.js +48 -36
- package/webwallgl.global.min.js.map +1 -1
- package/webwallgl.min.mjs +48 -36
- package/webwallgl.min.mjs.map +1 -1
- package/webwallgl.mjs +949 -117
- package/webwallgl.mjs.map +1 -1
package/webwallgl.mjs
CHANGED
|
@@ -995,6 +995,7 @@ function parseScene(sceneJson, project) {
|
|
|
995
995
|
// anchor 是盒子相对 origin 的锚点(同 image alignment 枚举,外加 "none")。
|
|
996
996
|
// 本机 563 个文字层:none 237 / 缺省 301 / center 20 —— none 与缺省都按 center 处理
|
|
997
997
|
// (WE 对象缺省对齐就是 center;显式 center 的挂件与时钟层行为一致)。
|
|
998
|
+
// 2780710296 实验过默认改 top:竖直阶梯对了,但会平移其它壁纸文字相对图元的位置,已回滚。
|
|
998
999
|
textAnchor: typeof o.anchor === "string" && o.anchor !== "none" ? o.anchor : "center",
|
|
999
1000
|
textMaxwidth: parseNum(o.maxwidth, 0),
|
|
1000
1001
|
textMaxrows: parseNum(o.maxrows, 0),
|
|
@@ -2425,6 +2426,45 @@ function hlsl2glsl(src, stage, combos, includeResolver, siblingSrc) {
|
|
|
2425
2426
|
}
|
|
2426
2427
|
}
|
|
2427
2428
|
}
|
|
2429
|
+
{
|
|
2430
|
+
const inVecN = /* @__PURE__ */ new Map();
|
|
2431
|
+
const declRe = /^\s*in\s+(?:highp|mediump|lowp\s+)?(vec[34])\s+([A-Za-z_]\w*)\s*;/gm;
|
|
2432
|
+
let dm;
|
|
2433
|
+
while ((dm = declRe.exec(code)) !== null) inVecN.set(dm[2], Number(dm[1].slice(3)));
|
|
2434
|
+
const localVecN = new Map(inVecN);
|
|
2435
|
+
const locRe = /\b(vec[34])\s+([A-Za-z_]\w*)\s*[=;]/g;
|
|
2436
|
+
while ((dm = locRe.exec(code)) !== null) {
|
|
2437
|
+
if (!localVecN.has(dm[2])) localVecN.set(dm[2], Number(dm[1].slice(3)));
|
|
2438
|
+
}
|
|
2439
|
+
if (localVecN.size > 0) {
|
|
2440
|
+
const swizzleUvArg = (arg) => {
|
|
2441
|
+
const t = arg.trim();
|
|
2442
|
+
if (!t) return arg;
|
|
2443
|
+
const bare = /^([A-Za-z_]\w*)$/.exec(t);
|
|
2444
|
+
if (bare && localVecN.has(bare[1])) return bare[1] + ".xy";
|
|
2445
|
+
const bin = /^([A-Za-z_]\w*)(\s*[+\-].+)$/.exec(t);
|
|
2446
|
+
if (bin && localVecN.has(bin[1])) return "(" + bin[1] + ".xy" + bin[2] + ")";
|
|
2447
|
+
return arg;
|
|
2448
|
+
};
|
|
2449
|
+
for (const fn of ["textureLod", "texture"]) {
|
|
2450
|
+
code = rewriteCall(code, fn, (inner) => {
|
|
2451
|
+
const args = splitArgs(inner);
|
|
2452
|
+
if (args.length >= 2) args[1] = swizzleUvArg(args[1]);
|
|
2453
|
+
return fn + "(" + args.join(", ") + ")";
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
if (inVecN.size > 0) {
|
|
2458
|
+
code = code.split("\n").map((line) => {
|
|
2459
|
+
if (!/\bvec2\s+[A-Za-z_]\w*\s*=/.test(line)) return line;
|
|
2460
|
+
let out = line;
|
|
2461
|
+
for (const name of inVecN.keys()) {
|
|
2462
|
+
out = out.replace(new RegExp("\\b" + name + "\\b(?!\\s*[.\\w])", "g"), name + ".xy");
|
|
2463
|
+
}
|
|
2464
|
+
return out;
|
|
2465
|
+
}).join("\n");
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2428
2468
|
const written = /* @__PURE__ */ new Set();
|
|
2429
2469
|
const inNames = /* @__PURE__ */ new Set();
|
|
2430
2470
|
{
|
|
@@ -2720,7 +2760,8 @@ function compile(gl, type, src) {
|
|
|
2720
2760
|
return s;
|
|
2721
2761
|
}
|
|
2722
2762
|
function parseVec3Local(s) {
|
|
2723
|
-
|
|
2763
|
+
if (s !== null && typeof s === "object" && "value" in s) s = s.value;
|
|
2764
|
+
const p = String(s ?? "").trim().split(/\s+/).map(Number);
|
|
2724
2765
|
return [p[0] || 0, p[1] || 0, p[2] || 0];
|
|
2725
2766
|
}
|
|
2726
2767
|
function makeTexture(gl, rgba, width, height, bitmap = null) {
|
|
@@ -3269,7 +3310,11 @@ function createRenderer(canvas, opts = {}) {
|
|
|
3269
3310
|
}
|
|
3270
3311
|
}
|
|
3271
3312
|
const key = shaderName + "|" + JSON.stringify(effectiveCombos);
|
|
3272
|
-
if (progCache.has(key))
|
|
3313
|
+
if (progCache.has(key)) {
|
|
3314
|
+
const hit = progCache.get(key);
|
|
3315
|
+
if (hit === null) throw new Error("shader=" + shaderName + " 编译失败(已缓存)");
|
|
3316
|
+
return hit;
|
|
3317
|
+
}
|
|
3273
3318
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
3274
3319
|
const missing = /* @__PURE__ */ new Set();
|
|
3275
3320
|
const resolver = (file) => {
|
|
@@ -3284,6 +3329,7 @@ function createRenderer(canvas, opts = {}) {
|
|
|
3284
3329
|
try {
|
|
3285
3330
|
prog = linkProgram(gl, vertGlsl, fragGlsl);
|
|
3286
3331
|
} catch (e) {
|
|
3332
|
+
progCache.set(key, null);
|
|
3287
3333
|
throw new Error("shader=" + shaderName + " " + (e && e.message));
|
|
3288
3334
|
}
|
|
3289
3335
|
const uni = /* @__PURE__ */ new Map();
|
|
@@ -4477,7 +4523,10 @@ function createRenderer(canvas, opts = {}) {
|
|
|
4477
4523
|
try {
|
|
4478
4524
|
progEntry = await getEffectProgram(mp.shader, combos, mergedTex);
|
|
4479
4525
|
} catch (e) {
|
|
4480
|
-
|
|
4526
|
+
const msg = e && e.message || String(e);
|
|
4527
|
+
if (!/已缓存/.test(msg)) {
|
|
4528
|
+
console.warn("[we-scene] 跳过效果(pass 编译失败):", mp.shader, msg);
|
|
4529
|
+
}
|
|
4481
4530
|
failedEffects.add(eff2);
|
|
4482
4531
|
continue;
|
|
4483
4532
|
}
|
|
@@ -4827,6 +4876,7 @@ layout(location=1) in vec3 a_pos; // 实例中心(投影空间世界
|
|
|
4827
4876
|
layout(location=2) in vec2 a_sizeRot; // x=size(像素) y=rot(弧度)
|
|
4828
4877
|
layout(location=3) in vec4 a_color; // rgb + alpha
|
|
4829
4878
|
layout(location=4) in vec3 a_stretchFrame; // xy=非等比拉伸 z=帧序号
|
|
4879
|
+
layout(location=5) in vec2 a_vrange; // 段两端沿贴图 v 的取值(rope 连线用;普通精灵 0..1)
|
|
4830
4880
|
uniform mat4 u_mvp;
|
|
4831
4881
|
// 序列帧 uv 变换表(TEXS 帧矩形归一化后的 offset/scale),最多 128 帧
|
|
4832
4882
|
// (matrix spritesheet 72 有 71 帧,旧上限 64 会丢末尾字符)
|
|
@@ -4844,7 +4894,8 @@ void main(){
|
|
|
4844
4894
|
gl_Position = u_mvp * vec4(a_pos.xy + rotated, a_pos.z, 1.0);
|
|
4845
4895
|
// quad 角 → 贴图 uv。世界 y 已翻转到投影空间(y 向下),故 quad 的 +y 角
|
|
4846
4896
|
// 对应屏幕上方,应采样纹理顶行 v=1(与 renderer.js 的 layerQuadVerts 同约定)。
|
|
4847
|
-
|
|
4897
|
+
// a_vrange 让 rope 段两端各取自己的 v(沿绳连续渐变);普通精灵是 (0,1) 恒等。
|
|
4898
|
+
vec2 uv = vec2(a_corner.x + 0.5, mix(a_vrange.x, a_vrange.y, a_corner.y + 0.5));
|
|
4848
4899
|
if (u_frameCount > 0) {
|
|
4849
4900
|
// 帧矩形以左上为原点(TEXS 是 top-down 像素坐标),故先把 v 翻成 top-down
|
|
4850
4901
|
int fi = int(a_stretchFrame.z);
|
|
@@ -4918,7 +4969,7 @@ void main(){
|
|
|
4918
4969
|
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 8, 0);
|
|
4919
4970
|
gl.vertexAttribDivisor(0, 0);
|
|
4920
4971
|
gl.bindBuffer(gl.ARRAY_BUFFER, vbuf);
|
|
4921
|
-
const S =
|
|
4972
|
+
const S = 56;
|
|
4922
4973
|
gl.enableVertexAttribArray(1);
|
|
4923
4974
|
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, S, 0);
|
|
4924
4975
|
gl.vertexAttribDivisor(1, 1);
|
|
@@ -4931,6 +4982,9 @@ void main(){
|
|
|
4931
4982
|
gl.enableVertexAttribArray(4);
|
|
4932
4983
|
gl.vertexAttribPointer(4, 3, gl.FLOAT, false, S, 36);
|
|
4933
4984
|
gl.vertexAttribDivisor(4, 1);
|
|
4985
|
+
gl.enableVertexAttribArray(5);
|
|
4986
|
+
gl.vertexAttribPointer(5, 2, gl.FLOAT, false, S, 48);
|
|
4987
|
+
gl.vertexAttribDivisor(5, 1);
|
|
4934
4988
|
gl.bindVertexArray(null);
|
|
4935
4989
|
return {
|
|
4936
4990
|
prog: {
|
|
@@ -4982,6 +5036,20 @@ function spriteTrailRotation(dx, dy) {
|
|
|
4982
5036
|
function particleInstanceSegs(trailCfg, trailSegments) {
|
|
4983
5037
|
return trailCfg && trailCfg.kind === "ropetrail" ? Math.max(1, trailSegments || 1) : 1;
|
|
4984
5038
|
}
|
|
5039
|
+
function ropeTrailHistoryCount(cfg) {
|
|
5040
|
+
if (!cfg || cfg.kind !== "ropetrail") return 1;
|
|
5041
|
+
const segs = Math.round(Number(cfg.segments) || 0);
|
|
5042
|
+
if (segs >= 2) return Math.min(32, segs);
|
|
5043
|
+
return 8;
|
|
5044
|
+
}
|
|
5045
|
+
function ropeTrailDuration(cfg) {
|
|
5046
|
+
if (!cfg || cfg.kind !== "ropetrail") return 0;
|
|
5047
|
+
const L = Number(cfg.length);
|
|
5048
|
+
return Number.isFinite(L) && L > 0 ? L : 0.2;
|
|
5049
|
+
}
|
|
5050
|
+
function ropeParticleV(p) {
|
|
5051
|
+
return p.life > 0 ? p.age / p.life : 0;
|
|
5052
|
+
}
|
|
4985
5053
|
class Particle {
|
|
4986
5054
|
constructor() {
|
|
4987
5055
|
this.alive = false;
|
|
@@ -5010,6 +5078,8 @@ class Particle {
|
|
|
5010
5078
|
this.turbSpeed = 0;
|
|
5011
5079
|
this.turbPhase = 0;
|
|
5012
5080
|
this.trail = null;
|
|
5081
|
+
this.trailClock = 0;
|
|
5082
|
+
this.seq = 0;
|
|
5013
5083
|
}
|
|
5014
5084
|
}
|
|
5015
5085
|
class ParticleSystem {
|
|
@@ -5070,6 +5140,9 @@ class ParticleSystem {
|
|
|
5070
5140
|
this._followParent = null;
|
|
5071
5141
|
this._followMode = null;
|
|
5072
5142
|
this._followOffset = [0, 0, 0];
|
|
5143
|
+
this.ropeRenderer = null;
|
|
5144
|
+
this._seq = 0;
|
|
5145
|
+
this._ropeOrder = [];
|
|
5073
5146
|
this._ov = {};
|
|
5074
5147
|
this._applyOverride();
|
|
5075
5148
|
this.startTime = Math.max(0, Math.min(30, num(this.model.starttime, 0)));
|
|
@@ -5346,24 +5419,34 @@ class ParticleSystem {
|
|
|
5346
5419
|
const kind = r && r.name || "sprite";
|
|
5347
5420
|
return {
|
|
5348
5421
|
kind,
|
|
5349
|
-
length: num(r && r.length, kind === "spritetrail" ? 0.1 : 0),
|
|
5422
|
+
length: num(r && r.length, kind === "spritetrail" ? 0.1 : kind === "ropetrail" ? 0.2 : 0),
|
|
5350
5423
|
maxLength: num(r && r.maxlength, 0),
|
|
5351
5424
|
minLength: num(r && r.minlength, 0),
|
|
5352
5425
|
subdivision: num(r && r.subdivision, 1),
|
|
5426
|
+
// Rope Trail 段数(官方 `segments`);与 spritetrail 的 maxlength 无关
|
|
5427
|
+
segments: num(r && r.segments, 0),
|
|
5353
5428
|
orientation: r && r.orientation || null
|
|
5354
5429
|
};
|
|
5355
5430
|
});
|
|
5356
5431
|
if (omitted && !this.renderers.length) {
|
|
5357
|
-
this.renderers = [{ kind: "sprite", length: 0, maxLength: 0, minLength: 0, subdivision: 1, orientation: null }];
|
|
5432
|
+
this.renderers = [{ kind: "sprite", length: 0, maxLength: 0, minLength: 0, subdivision: 1, segments: 0, orientation: null }];
|
|
5358
5433
|
}
|
|
5359
5434
|
const tr = this.renderers.find((r) => r.kind === "spritetrail" || r.kind === "ropetrail");
|
|
5360
5435
|
this.trailCfg = tr || null;
|
|
5361
5436
|
this.trailSegments = 1;
|
|
5437
|
+
this.trailDuration = 0;
|
|
5438
|
+
this.trailSampleDt = 0;
|
|
5362
5439
|
if (tr && tr.kind === "ropetrail") {
|
|
5363
|
-
const segs =
|
|
5440
|
+
const segs = ropeTrailHistoryCount(tr);
|
|
5364
5441
|
this.trailSegments = segs;
|
|
5365
|
-
|
|
5442
|
+
this.trailDuration = ropeTrailDuration(tr);
|
|
5443
|
+
this.trailSampleDt = this.trailDuration / Math.max(1, segs - 1);
|
|
5444
|
+
for (const p of this.pool) {
|
|
5445
|
+
p.trail = new Float32Array(segs * 3);
|
|
5446
|
+
p.trailClock = 0;
|
|
5447
|
+
}
|
|
5366
5448
|
}
|
|
5449
|
+
this.ropeRenderer = this.renderers.find((r) => r.kind === "rope") || null;
|
|
5367
5450
|
}
|
|
5368
5451
|
setModel(model) {
|
|
5369
5452
|
this.model = model;
|
|
@@ -5494,6 +5577,7 @@ class ParticleSystem {
|
|
|
5494
5577
|
p.rotVel = 0;
|
|
5495
5578
|
p.vx = p.vy = p.vz = 0;
|
|
5496
5579
|
p.frame = 0;
|
|
5580
|
+
p.seq = this._seq++;
|
|
5497
5581
|
const o = em.origin;
|
|
5498
5582
|
if (em.kind === "box") {
|
|
5499
5583
|
const d = em.distanceMax || [0, 0, 0];
|
|
@@ -5640,6 +5724,7 @@ class ParticleSystem {
|
|
|
5640
5724
|
p.trail[i + 1] = p.y;
|
|
5641
5725
|
p.trail[i + 2] = p.z;
|
|
5642
5726
|
}
|
|
5727
|
+
p.trailClock = 0;
|
|
5643
5728
|
}
|
|
5644
5729
|
}
|
|
5645
5730
|
// ---------- 每粒子更新 ----------
|
|
@@ -5790,10 +5875,20 @@ class ParticleSystem {
|
|
|
5790
5875
|
}
|
|
5791
5876
|
if (p.trail) {
|
|
5792
5877
|
const tr = p.trail;
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5878
|
+
const step = this.trailSampleDt;
|
|
5879
|
+
if (step > 0) {
|
|
5880
|
+
p.trailClock = (p.trailClock || 0) + dt;
|
|
5881
|
+
let shifts = 0;
|
|
5882
|
+
const cap = this.trailSegments || 8;
|
|
5883
|
+
while (p.trailClock >= step && shifts < cap) {
|
|
5884
|
+
p.trailClock -= step;
|
|
5885
|
+
shifts++;
|
|
5886
|
+
for (let i = tr.length - 3; i >= 3; i -= 3) {
|
|
5887
|
+
tr[i] = tr[i - 3];
|
|
5888
|
+
tr[i + 1] = tr[i - 2];
|
|
5889
|
+
tr[i + 2] = tr[i - 1];
|
|
5890
|
+
}
|
|
5891
|
+
}
|
|
5797
5892
|
}
|
|
5798
5893
|
tr[0] = p.x;
|
|
5799
5894
|
tr[1] = p.y;
|
|
@@ -5888,13 +5983,22 @@ class ParticleSystem {
|
|
|
5888
5983
|
if (!this._prog) this._buildProgram(gl);
|
|
5889
5984
|
const trail = this.trailCfg && this.trailCfg.kind === "ropetrail" ? this.trailCfg : null;
|
|
5890
5985
|
const spriteTrail = this.trailCfg && this.trailCfg.kind === "spritetrail" ? this.trailCfg : null;
|
|
5986
|
+
const rope = this.ropeRenderer;
|
|
5891
5987
|
const segs = particleInstanceSegs(this.trailCfg, this.trailSegments);
|
|
5892
|
-
const STRIDE =
|
|
5988
|
+
const STRIDE = 14;
|
|
5893
5989
|
const pool = this.pool;
|
|
5990
|
+
let order = null;
|
|
5991
|
+
if (rope) {
|
|
5992
|
+
order = this._ropeOrder;
|
|
5993
|
+
order.length = 0;
|
|
5994
|
+
for (let i = 0; i < pool.length; i++) if (pool[i].alive) order.push(pool[i]);
|
|
5995
|
+
order.sort((a, b) => a.seq - b.seq);
|
|
5996
|
+
}
|
|
5894
5997
|
let live = 0;
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5998
|
+
if (order) live = order.length;
|
|
5999
|
+
else for (let i = 0; i < pool.length; i++) if (pool[i].alive) live++;
|
|
6000
|
+
if (live === 0 || rope && live < 2) return;
|
|
6001
|
+
const instCount = rope ? live - 1 : live * segs;
|
|
5898
6002
|
const need = instCount * STRIDE;
|
|
5899
6003
|
if (!this._data || this._data.length < need) this._data = new Float32Array(Math.max(need, 1024));
|
|
5900
6004
|
const data = this._data;
|
|
@@ -5914,7 +6018,34 @@ class ParticleSystem {
|
|
|
5914
6018
|
const py = ly * sy;
|
|
5915
6019
|
return [ox + px * cos - py * sin, projH - (oy + px * sin + py * cos)];
|
|
5916
6020
|
};
|
|
5917
|
-
|
|
6021
|
+
if (rope) {
|
|
6022
|
+
for (let i = 0; i + 1 < live; i++) {
|
|
6023
|
+
const a = order[i];
|
|
6024
|
+
const b = order[i + 1];
|
|
6025
|
+
const wa = toWorld(a.x, a.y);
|
|
6026
|
+
const wb = toWorld(b.x, b.y);
|
|
6027
|
+
const dx = wb[0] - wa[0];
|
|
6028
|
+
const dy = wb[1] - wa[1];
|
|
6029
|
+
const dist = Math.hypot(dx, dy);
|
|
6030
|
+
const width2 = (Math.abs(a.size) + Math.abs(b.size)) * 0.5 * sysScale;
|
|
6031
|
+
if (!(width2 > 0)) continue;
|
|
6032
|
+
data[k++] = (wa[0] + wb[0]) * 0.5;
|
|
6033
|
+
data[k++] = (wa[1] + wb[1]) * 0.5;
|
|
6034
|
+
data[k++] = 0;
|
|
6035
|
+
data[k++] = width2;
|
|
6036
|
+
data[k++] = Math.atan2(-dx, dy);
|
|
6037
|
+
data[k++] = (a.r + b.r) * 0.5 * bright;
|
|
6038
|
+
data[k++] = (a.g + b.g) * 0.5 * bright;
|
|
6039
|
+
data[k++] = (a.b + b.b) * 0.5 * bright;
|
|
6040
|
+
data[k++] = (a.alpha + b.alpha) * 0.5;
|
|
6041
|
+
data[k++] = 1;
|
|
6042
|
+
data[k++] = dist / width2;
|
|
6043
|
+
data[k++] = 0;
|
|
6044
|
+
data[k++] = ropeParticleV(a);
|
|
6045
|
+
data[k++] = ropeParticleV(b);
|
|
6046
|
+
}
|
|
6047
|
+
}
|
|
6048
|
+
for (let i = 0; i < pool.length && !rope; i++) {
|
|
5918
6049
|
const p = pool[i];
|
|
5919
6050
|
if (!p.alive) continue;
|
|
5920
6051
|
for (let s = 0; s < segs; s++) {
|
|
@@ -5922,18 +6053,53 @@ class ParticleSystem {
|
|
|
5922
6053
|
let ly = p.y;
|
|
5923
6054
|
let segAlpha = 1;
|
|
5924
6055
|
let segSize = 1;
|
|
6056
|
+
let rot = p.rot;
|
|
6057
|
+
let instStretchX = stretchX;
|
|
6058
|
+
let instStretchY = stretchY;
|
|
6059
|
+
let wx;
|
|
6060
|
+
let wy;
|
|
5925
6061
|
if (trail && p.trail) {
|
|
5926
6062
|
lx = p.trail[s * 3];
|
|
5927
6063
|
ly = p.trail[s * 3 + 1];
|
|
5928
6064
|
const t = segs > 1 ? s / (segs - 1) : 0;
|
|
5929
6065
|
segAlpha = 1 - t;
|
|
5930
6066
|
segSize = 1 - t * 0.55;
|
|
6067
|
+
let tdx = 0;
|
|
6068
|
+
let tdy = 0;
|
|
6069
|
+
if (s + 1 < segs) {
|
|
6070
|
+
tdx = p.trail[s * 3] - p.trail[(s + 1) * 3];
|
|
6071
|
+
tdy = p.trail[s * 3 + 1] - p.trail[(s + 1) * 3 + 1];
|
|
6072
|
+
} else if (s > 0) {
|
|
6073
|
+
tdx = p.trail[(s - 1) * 3] - p.trail[s * 3];
|
|
6074
|
+
tdy = p.trail[(s - 1) * 3 + 1] - p.trail[s * 3 + 1];
|
|
6075
|
+
} else {
|
|
6076
|
+
tdx = p.vx;
|
|
6077
|
+
tdy = p.vy;
|
|
6078
|
+
}
|
|
6079
|
+
const w0 = toWorld(lx, ly);
|
|
6080
|
+
const w1 = toWorld(lx - tdx, ly - tdy);
|
|
6081
|
+
const dx = w0[0] - w1[0];
|
|
6082
|
+
const dy = w0[1] - w1[1];
|
|
6083
|
+
const dist = Math.hypot(dx, dy);
|
|
6084
|
+
const base = Math.max(1e-3, Math.abs(p.size) * sysScale * segSize);
|
|
6085
|
+
if (dist > 1e-3) {
|
|
6086
|
+
rot = spriteTrailRotation(dx, dy);
|
|
6087
|
+
wx = (w0[0] + w1[0]) * 0.5;
|
|
6088
|
+
wy = (w0[1] + w1[1]) * 0.5;
|
|
6089
|
+
instStretchY = Math.max(stretchY, dist / base);
|
|
6090
|
+
} else {
|
|
6091
|
+
wx = w0[0];
|
|
6092
|
+
wy = w0[1];
|
|
6093
|
+
}
|
|
6094
|
+
} else {
|
|
6095
|
+
const w = toWorld(lx, ly);
|
|
6096
|
+
wx = w[0];
|
|
6097
|
+
wy = w[1];
|
|
5931
6098
|
}
|
|
5932
|
-
const w = toWorld(lx, ly);
|
|
5933
|
-
let rot = p.rot;
|
|
5934
|
-
let instStretchX = stretchX;
|
|
5935
|
-
let instStretchY = stretchY;
|
|
5936
6099
|
if (spriteTrail) {
|
|
6100
|
+
const w = toWorld(p.x, p.y);
|
|
6101
|
+
wx = w[0];
|
|
6102
|
+
wy = w[1];
|
|
5937
6103
|
const w1 = toWorld(p.x + p.vx, p.y + p.vy);
|
|
5938
6104
|
rot = spriteTrailRotation(w1[0] - w[0], w1[1] - w[1]);
|
|
5939
6105
|
const factor = spriteTrailLengthFactor(
|
|
@@ -5944,8 +6110,8 @@ class ParticleSystem {
|
|
|
5944
6110
|
);
|
|
5945
6111
|
instStretchY = stretchY * factor;
|
|
5946
6112
|
}
|
|
5947
|
-
data[k++] =
|
|
5948
|
-
data[k++] =
|
|
6113
|
+
data[k++] = wx;
|
|
6114
|
+
data[k++] = wy;
|
|
5949
6115
|
data[k++] = 0;
|
|
5950
6116
|
data[k++] = Math.abs(p.size) * sysScale * segSize;
|
|
5951
6117
|
data[k++] = rot;
|
|
@@ -5956,6 +6122,8 @@ class ParticleSystem {
|
|
|
5956
6122
|
data[k++] = instStretchX;
|
|
5957
6123
|
data[k++] = instStretchY;
|
|
5958
6124
|
data[k++] = p.frame;
|
|
6125
|
+
data[k++] = 0;
|
|
6126
|
+
data[k++] = 1;
|
|
5959
6127
|
}
|
|
5960
6128
|
}
|
|
5961
6129
|
const prog = this._prog;
|
|
@@ -6089,6 +6257,9 @@ const particlesMod = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.define
|
|
|
6089
6257
|
particleInstanceSegs,
|
|
6090
6258
|
particlePassRefract,
|
|
6091
6259
|
rgbaIsBlankWhite,
|
|
6260
|
+
ropeParticleV,
|
|
6261
|
+
ropeTrailDuration,
|
|
6262
|
+
ropeTrailHistoryCount,
|
|
6092
6263
|
spriteTrailLengthFactor,
|
|
6093
6264
|
spriteTrailRotation
|
|
6094
6265
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
@@ -6194,7 +6365,7 @@ function spikyStar(size, points, len, sharp, coreR) {
|
|
|
6194
6365
|
return { width: size, height: size, rgba };
|
|
6195
6366
|
}
|
|
6196
6367
|
function beam(w, h, coreWidth, fadeBoth, peak) {
|
|
6197
|
-
const pk =
|
|
6368
|
+
const pk = 0.55;
|
|
6198
6369
|
const rgba = new Uint8Array(w * h * 4);
|
|
6199
6370
|
for (let y = 0; y < h; y++) {
|
|
6200
6371
|
const ty = y / (h - 1);
|
|
@@ -6207,6 +6378,41 @@ function beam(w, h, coreWidth, fadeBoth, peak) {
|
|
|
6207
6378
|
}
|
|
6208
6379
|
return { width: w, height: h, rgba };
|
|
6209
6380
|
}
|
|
6381
|
+
const RAIN_FRAMES = 4;
|
|
6382
|
+
function rainStreak(w, h, tiltDeg, corePx, peak) {
|
|
6383
|
+
const pk = peak === void 0 ? 0.85 : peak;
|
|
6384
|
+
const rgba = new Uint8Array(w * h * 4);
|
|
6385
|
+
const fh = h / RAIN_FRAMES;
|
|
6386
|
+
const rng = mulberry32$1(335009);
|
|
6387
|
+
const tilt = Math.tan(tiltDeg * Math.PI / 180);
|
|
6388
|
+
const cx = w / 2;
|
|
6389
|
+
for (let f = 0; f < RAIN_FRAMES; f++) {
|
|
6390
|
+
const peakF = pk * (0.8 + rng() * 0.35);
|
|
6391
|
+
const coreF = corePx * (0.85 + rng() * 0.5);
|
|
6392
|
+
const ph = (rng() - 0.5) * w * 0.12;
|
|
6393
|
+
for (let y = 0; y < fh; y++) {
|
|
6394
|
+
const ty = y / (fh - 1);
|
|
6395
|
+
const lineX = cx + ph + (fh - 1) * tilt / 2 - (fh - 1) * tilt * ty;
|
|
6396
|
+
const vy = gauss(ty - 0.5, 0.27);
|
|
6397
|
+
for (let x = 0; x < w; x++) {
|
|
6398
|
+
const d = Math.abs(x + 0.5 - lineX);
|
|
6399
|
+
const g = Math.exp(-(d * d) / (coreF * coreF));
|
|
6400
|
+
const a = g * vy * peakF;
|
|
6401
|
+
if (a < 4e-3) continue;
|
|
6402
|
+
writeWhite(rgba, ((f * fh + y) * w + x) * 4, a);
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6405
|
+
}
|
|
6406
|
+
return { width: w, height: h, rgba };
|
|
6407
|
+
}
|
|
6408
|
+
function builtinParticleFrames(name) {
|
|
6409
|
+
if (name === "particle/nature/rain1" || name === "particle/nature/rain2") {
|
|
6410
|
+
const list = [];
|
|
6411
|
+
for (let i = 0; i < RAIN_FRAMES; i++) list.push({ ou: 0, ov: i / RAIN_FRAMES, su: 1, sv: 1 / RAIN_FRAMES });
|
|
6412
|
+
return list;
|
|
6413
|
+
}
|
|
6414
|
+
return null;
|
|
6415
|
+
}
|
|
6210
6416
|
function ring(size, radius, thickness) {
|
|
6211
6417
|
const rgba = new Uint8Array(size * size * 4);
|
|
6212
6418
|
const half = size / 2;
|
|
@@ -6887,8 +7093,8 @@ const BUILDERS = {
|
|
|
6887
7093
|
// 水滴(原生 64×256 → 128×512):竖长泪滴
|
|
6888
7094
|
"particle/drop": () => teardrop(128, 512),
|
|
6889
7095
|
// 雨丝(原生 64×256 → 128×512):细长条,两端渐隐
|
|
6890
|
-
"particle/nature/rain1": () =>
|
|
6891
|
-
"particle/nature/rain2": () =>
|
|
7096
|
+
"particle/nature/rain1": () => rainStreak(128, 512, 10, 1, 0.78),
|
|
7097
|
+
"particle/nature/rain2": () => rainStreak(128, 512, 10, 1.6, 0.66),
|
|
6892
7098
|
// 雨滴 sheet(原生 128×256 → 256×512,2×4 格):每格一颗上圆下尖小水滴
|
|
6893
7099
|
"particle/water/rain_drops_sheet": () => dropSheet(256, 512, 2, 4),
|
|
6894
7100
|
// 雾(原生 256 → 512):絮状 fBm,弱遮罩铺满;三张不同尺度/种子
|
|
@@ -7006,6 +7212,7 @@ function isBuiltinParticleTextureName(name) {
|
|
|
7006
7212
|
const particleTexMod = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
7007
7213
|
__proto__: null,
|
|
7008
7214
|
buildBuiltinParticleTexture,
|
|
7215
|
+
builtinParticleFrames,
|
|
7009
7216
|
isBuiltinParticleTextureName,
|
|
7010
7217
|
listBuiltinParticleTextureNames,
|
|
7011
7218
|
particleNormalNameForAlbedo,
|
|
@@ -7623,6 +7830,10 @@ function applyAttachmentBindOrigins(layers) {
|
|
|
7623
7830
|
c.origin[0] += d[0];
|
|
7624
7831
|
c.origin[1] += d[1];
|
|
7625
7832
|
}
|
|
7833
|
+
layer.parallaxDepth = parent.parallaxDepth ? parent.parallaxDepth.slice() : null;
|
|
7834
|
+
for (const c of desc) {
|
|
7835
|
+
c.parallaxDepth = layer.parallaxDepth ? layer.parallaxDepth.slice() : null;
|
|
7836
|
+
}
|
|
7626
7837
|
follows.push({
|
|
7627
7838
|
layer,
|
|
7628
7839
|
parent,
|
|
@@ -7930,10 +8141,12 @@ function layoutText(content, opts, measure) {
|
|
|
7930
8141
|
const totalH = lines.length * lineHeight;
|
|
7931
8142
|
const halign = opts.halign || "center";
|
|
7932
8143
|
const valign = opts.valign || "center";
|
|
7933
|
-
const
|
|
8144
|
+
const midX = boxW / 2;
|
|
8145
|
+
const midY = boxH / 2;
|
|
8146
|
+
const y0 = valign === "top" ? midY : valign === "bottom" ? midY - totalH : (boxH - totalH) / 2;
|
|
7934
8147
|
const out = lines.map((text, i) => {
|
|
7935
8148
|
const w = widths[i];
|
|
7936
|
-
const x = halign === "left" ?
|
|
8149
|
+
const x = halign === "left" ? midX : halign === "right" ? midX - w : (boxW - w) / 2;
|
|
7937
8150
|
return { text, width: w, x, y: y0 + i * lineHeight };
|
|
7938
8151
|
});
|
|
7939
8152
|
return { lines: out, lineHeight, totalH, truncated, boxW, boxH };
|
|
@@ -8308,6 +8521,11 @@ function propValue(v) {
|
|
|
8308
8521
|
if (v !== null && typeof v === "object" && "value" in v) return v.value;
|
|
8309
8522
|
return v;
|
|
8310
8523
|
}
|
|
8524
|
+
function engineCanvasSize(cs) {
|
|
8525
|
+
const src = cs || { width: 1920, height: 1080 };
|
|
8526
|
+
if (src.x !== void 0 && src.y !== void 0) return src;
|
|
8527
|
+
return { x: src.width, y: src.height, width: src.width, height: src.height };
|
|
8528
|
+
}
|
|
8311
8529
|
function evalTextScript(script, scriptprops, opts = {}) {
|
|
8312
8530
|
if (typeof script !== "string" || script.length === 0) return null;
|
|
8313
8531
|
const body = scriptToFunctionBody(script);
|
|
@@ -8349,7 +8567,7 @@ function evalTextScript(script, scriptprops, opts = {}) {
|
|
|
8349
8567
|
},
|
|
8350
8568
|
frametime: 1 / 60,
|
|
8351
8569
|
runtime: 0,
|
|
8352
|
-
canvasSize: opts.canvasSize
|
|
8570
|
+
canvasSize: engineCanvasSize(opts.canvasSize),
|
|
8353
8571
|
// [we-scene patch] engine.screenResolution(全库 10 处 / 2 壁纸):
|
|
8354
8572
|
// 屏幕**像素**尺寸,脚本用它把 input.cursorScreenPosition 归一化
|
|
8355
8573
|
// (3791967416 除它得 [0,1];3509243656 减半屏得 [-1,1])。
|
|
@@ -9299,7 +9517,7 @@ function evalObjectScript(script, scriptprops, opts = {}) {
|
|
|
9299
9517
|
},
|
|
9300
9518
|
frametime: 1 / 60,
|
|
9301
9519
|
runtime: 0,
|
|
9302
|
-
canvasSize: opts.canvasSize
|
|
9520
|
+
canvasSize: engineCanvasSize(opts.canvasSize),
|
|
9303
9521
|
screenResolution: opts.screenResolution || { x: 1920, y: 1080 },
|
|
9304
9522
|
timeOfDay: typeof opts.timeOfDay === "number" ? opts.timeOfDay : 0,
|
|
9305
9523
|
userProperties: opts.userProperties || {},
|
|
@@ -9734,7 +9952,7 @@ function createEngineTimers(host = {}, opts = {}) {
|
|
|
9734
9952
|
cancel.handle = state.handle;
|
|
9735
9953
|
return cancel;
|
|
9736
9954
|
}
|
|
9737
|
-
function
|
|
9955
|
+
function setInterval2(fn, ms) {
|
|
9738
9956
|
if (typeof fn !== "function") return makeCancel({ fired: true, handle: null }, null);
|
|
9739
9957
|
const state = { fired: false, handle: null };
|
|
9740
9958
|
const cancel = makeCancel(state, clearI);
|
|
@@ -9750,7 +9968,7 @@ function createEngineTimers(host = {}, opts = {}) {
|
|
|
9750
9968
|
}
|
|
9751
9969
|
if (clearT && h != null) clearT(h);
|
|
9752
9970
|
}
|
|
9753
|
-
function
|
|
9971
|
+
function clearInterval2(h) {
|
|
9754
9972
|
if (typeof h === "function") {
|
|
9755
9973
|
h();
|
|
9756
9974
|
return;
|
|
@@ -9763,9 +9981,9 @@ function createEngineTimers(host = {}, opts = {}) {
|
|
|
9763
9981
|
}
|
|
9764
9982
|
return {
|
|
9765
9983
|
setTimeout,
|
|
9766
|
-
setInterval,
|
|
9984
|
+
setInterval: setInterval2,
|
|
9767
9985
|
clearTimeout,
|
|
9768
|
-
clearInterval,
|
|
9986
|
+
clearInterval: clearInterval2,
|
|
9769
9987
|
dispose,
|
|
9770
9988
|
/** 测试与诊断用:尚未触发且未取消的定时器数 */
|
|
9771
9989
|
pendingCount: () => pending.size
|
|
@@ -9815,11 +10033,11 @@ function createSimulatedAudio(seed = 20260830) {
|
|
|
9815
10033
|
const patterns = makePatterns(rand2);
|
|
9816
10034
|
const phases = new Float32Array(64);
|
|
9817
10035
|
for (let i = 0; i < 64; i++) phases[i] = rand2() * 64;
|
|
9818
|
-
const
|
|
9819
|
-
const rawL = new Float32Array(
|
|
9820
|
-
const rawR = new Float32Array(
|
|
9821
|
-
const left64 = new Float32Array(
|
|
9822
|
-
const right64 = new Float32Array(
|
|
10036
|
+
const BANDS2 = 64;
|
|
10037
|
+
const rawL = new Float32Array(BANDS2);
|
|
10038
|
+
const rawR = new Float32Array(BANDS2);
|
|
10039
|
+
const left64 = new Float32Array(BANDS2);
|
|
10040
|
+
const right64 = new Float32Array(BANDS2);
|
|
9823
10041
|
const left32 = new Float32Array(32);
|
|
9824
10042
|
const right32 = new Float32Array(32);
|
|
9825
10043
|
const left16 = new Float32Array(16);
|
|
@@ -9836,7 +10054,7 @@ function createSimulatedAudio(seed = 20260830) {
|
|
|
9836
10054
|
/** 渲染器诊断:当前是否处于「静音段」 */
|
|
9837
10055
|
silent: false
|
|
9838
10056
|
};
|
|
9839
|
-
function
|
|
10057
|
+
function downsample2(dst, src) {
|
|
9840
10058
|
const g = src.length / dst.length;
|
|
9841
10059
|
for (let i = 0; i < dst.length; i++) {
|
|
9842
10060
|
let s = 0;
|
|
@@ -9863,8 +10081,8 @@ function createSimulatedAudio(seed = 20260830) {
|
|
|
9863
10081
|
const hatV = patterns.hat[i16] * hitEnv * drumGate;
|
|
9864
10082
|
const riser = buildup > 0 ? Math.pow(buildup, 3) * (0.4 + 0.6 * Math.abs(vnoise(step * 2, 7))) : 0;
|
|
9865
10083
|
let levelSum = 0;
|
|
9866
|
-
for (let i = 0; i <
|
|
9867
|
-
const fq = i /
|
|
10084
|
+
for (let i = 0; i < BANDS2; i++) {
|
|
10085
|
+
const fq = i / BANDS2;
|
|
9868
10086
|
const tilt = Math.pow(1 - fq * 0.85, 1.6);
|
|
9869
10087
|
let v = midGate * (0.5 + 0.3 * vnoise(beat * 0.5 + phases[i] * 0.05, i % 8));
|
|
9870
10088
|
v *= 0.35 + 0.65 * fq;
|
|
@@ -9888,10 +10106,10 @@ function createSimulatedAudio(seed = 20260830) {
|
|
|
9888
10106
|
}
|
|
9889
10107
|
left64.set(rawL);
|
|
9890
10108
|
right64.set(rawR);
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
|
|
10109
|
+
downsample2(left32, rawL);
|
|
10110
|
+
downsample2(right32, rawR);
|
|
10111
|
+
downsample2(left16, rawL);
|
|
10112
|
+
downsample2(right16, rawR);
|
|
9895
10113
|
snapshot.level = Math.min(1, levelSum / (48 * 1.2));
|
|
9896
10114
|
snapshot.silent = silent;
|
|
9897
10115
|
return snapshot;
|
|
@@ -9900,7 +10118,7 @@ function createSimulatedAudio(seed = 20260830) {
|
|
|
9900
10118
|
update,
|
|
9901
10119
|
snapshot,
|
|
9902
10120
|
/** 频段基数 */
|
|
9903
|
-
bands:
|
|
10121
|
+
bands: BANDS2
|
|
9904
10122
|
};
|
|
9905
10123
|
}
|
|
9906
10124
|
function fillAudioBuffers(views, snapshot) {
|
|
@@ -9922,7 +10140,7 @@ function fillOne(dst, src64) {
|
|
|
9922
10140
|
dst[i] = s / (i1 - i0);
|
|
9923
10141
|
}
|
|
9924
10142
|
}
|
|
9925
|
-
const MEDIA_PLAYBACK = { STOPPED: 0, PLAYING: 1, PAUSED: 2 };
|
|
10143
|
+
const MEDIA_PLAYBACK$1 = { STOPPED: 0, PLAYING: 1, PAUSED: 2 };
|
|
9926
10144
|
class MediaVec3 {
|
|
9927
10145
|
constructor(x, y, z) {
|
|
9928
10146
|
this.x = Number(x) || 0;
|
|
@@ -10047,7 +10265,7 @@ function createSimulatedMedia(seed = 20260901) {
|
|
|
10047
10265
|
const cycle = tracks.reduce((s, t) => s + t.duration + GAP, 0);
|
|
10048
10266
|
const snapshot = {
|
|
10049
10267
|
hasMedia: false,
|
|
10050
|
-
state: MEDIA_PLAYBACK.STOPPED,
|
|
10268
|
+
state: MEDIA_PLAYBACK$1.STOPPED,
|
|
10051
10269
|
title: "",
|
|
10052
10270
|
artist: "",
|
|
10053
10271
|
album: "",
|
|
@@ -10114,10 +10332,10 @@ function createSimulatedMedia(seed = 20260901) {
|
|
|
10114
10332
|
snapshot.duration = tr.duration;
|
|
10115
10333
|
snapshot.position = pos;
|
|
10116
10334
|
const frac = tr.duration > 0 ? pos / tr.duration : 0;
|
|
10117
|
-
if (held) snapshot.state = MEDIA_PLAYBACK.PAUSED;
|
|
10118
|
-
else if (inGap) snapshot.state = MEDIA_PLAYBACK.STOPPED;
|
|
10119
|
-
else if (frac > 0.7 && frac < 0.76) snapshot.state = MEDIA_PLAYBACK.PAUSED;
|
|
10120
|
-
else snapshot.state = MEDIA_PLAYBACK.PLAYING;
|
|
10335
|
+
if (held) snapshot.state = MEDIA_PLAYBACK$1.PAUSED;
|
|
10336
|
+
else if (inGap) snapshot.state = MEDIA_PLAYBACK$1.STOPPED;
|
|
10337
|
+
else if (frac > 0.7 && frac < 0.76) snapshot.state = MEDIA_PLAYBACK$1.PAUSED;
|
|
10338
|
+
else snapshot.state = MEDIA_PLAYBACK$1.PLAYING;
|
|
10121
10339
|
snapshot.hasThumbnail = !inGap;
|
|
10122
10340
|
const c = tr.colors;
|
|
10123
10341
|
snapshot.primaryColor = new MediaVec3(c.primary[0], c.primary[1], c.primary[2]);
|
|
@@ -10156,7 +10374,7 @@ function createSimulatedMedia(seed = 20260901) {
|
|
|
10156
10374
|
if (held) return snapshot;
|
|
10157
10375
|
holdT = lastWall + seekOffset;
|
|
10158
10376
|
held = true;
|
|
10159
|
-
snapshot.state = MEDIA_PLAYBACK.PAUSED;
|
|
10377
|
+
snapshot.state = MEDIA_PLAYBACK$1.PAUSED;
|
|
10160
10378
|
return snapshot;
|
|
10161
10379
|
}
|
|
10162
10380
|
function play() {
|
|
@@ -10173,7 +10391,7 @@ function createSimulatedMedia(seed = 20260901) {
|
|
|
10173
10391
|
snapshot,
|
|
10174
10392
|
tracks,
|
|
10175
10393
|
cycle,
|
|
10176
|
-
MEDIA_PLAYBACK,
|
|
10394
|
+
MEDIA_PLAYBACK: MEDIA_PLAYBACK$1,
|
|
10177
10395
|
skipNext,
|
|
10178
10396
|
skipPrevious,
|
|
10179
10397
|
play,
|
|
@@ -10281,7 +10499,7 @@ function cloneMediaSnapshot(s) {
|
|
|
10281
10499
|
}
|
|
10282
10500
|
const mediaMod = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
10283
10501
|
__proto__: null,
|
|
10284
|
-
MEDIA_PLAYBACK,
|
|
10502
|
+
MEDIA_PLAYBACK: MEDIA_PLAYBACK$1,
|
|
10285
10503
|
cloneMediaSnapshot,
|
|
10286
10504
|
createSimulatedMedia,
|
|
10287
10505
|
diffMediaEvents,
|
|
@@ -11145,6 +11363,382 @@ const SKIP_TEXT = false;
|
|
|
11145
11363
|
const SKIP_PARTICLES = false;
|
|
11146
11364
|
const SKIP_SCENE_EFFECTS = false;
|
|
11147
11365
|
const TEXT_EM_SCALE = 4;
|
|
11366
|
+
const BANDS = 64;
|
|
11367
|
+
const MEDIA_PLAYBACK = { STOPPED: 0 };
|
|
11368
|
+
function zeroBands() {
|
|
11369
|
+
return {
|
|
11370
|
+
left64: new Float32Array(BANDS),
|
|
11371
|
+
right64: new Float32Array(BANDS),
|
|
11372
|
+
left32: new Float32Array(32),
|
|
11373
|
+
right32: new Float32Array(32),
|
|
11374
|
+
left16: new Float32Array(16),
|
|
11375
|
+
right16: new Float32Array(16),
|
|
11376
|
+
level: 0,
|
|
11377
|
+
silent: true
|
|
11378
|
+
};
|
|
11379
|
+
}
|
|
11380
|
+
function downsample(dst, src64) {
|
|
11381
|
+
const g = src64.length / dst.length;
|
|
11382
|
+
for (let i = 0; i < dst.length; i++) {
|
|
11383
|
+
let s = 0;
|
|
11384
|
+
const i0 = Math.floor(i * g);
|
|
11385
|
+
const i1 = Math.max(i0 + 1, Math.floor((i + 1) * g));
|
|
11386
|
+
for (let j = i0; j < i1; j++) s += src64[j];
|
|
11387
|
+
dst[i] = s / (i1 - i0);
|
|
11388
|
+
}
|
|
11389
|
+
}
|
|
11390
|
+
function fillFromByteFreq(out, bytes, sampleRate) {
|
|
11391
|
+
const n = bytes.length;
|
|
11392
|
+
const nyquist = sampleRate * 0.5;
|
|
11393
|
+
const fMin = 20;
|
|
11394
|
+
const fMax = Math.min(2e4, nyquist);
|
|
11395
|
+
let levelSum = 0;
|
|
11396
|
+
for (let b = 0; b < BANDS; b++) {
|
|
11397
|
+
const t0 = b / BANDS;
|
|
11398
|
+
const t1 = (b + 1) / BANDS;
|
|
11399
|
+
const loHz = fMin * Math.pow(fMax / fMin, t0);
|
|
11400
|
+
const hiHz = fMin * Math.pow(fMax / fMin, t1);
|
|
11401
|
+
const i0 = Math.max(0, Math.floor(loHz / nyquist * n));
|
|
11402
|
+
const i1 = Math.min(n, Math.max(i0 + 1, Math.ceil(hiHz / nyquist * n)));
|
|
11403
|
+
let s = 0;
|
|
11404
|
+
for (let i = i0; i < i1; i++) s += bytes[i] / 255;
|
|
11405
|
+
const v = Math.min(1, s / (i1 - i0) * 1.35);
|
|
11406
|
+
out.left64[b] = v;
|
|
11407
|
+
out.right64[b] = v;
|
|
11408
|
+
if (b < 48) levelSum += v;
|
|
11409
|
+
}
|
|
11410
|
+
downsample(out.left32, out.left64);
|
|
11411
|
+
downsample(out.right32, out.right64);
|
|
11412
|
+
downsample(out.left16, out.left64);
|
|
11413
|
+
downsample(out.right16, out.right64);
|
|
11414
|
+
out.level = Math.min(1, levelSum / (48 * 1.2));
|
|
11415
|
+
out.silent = out.level < 0.02;
|
|
11416
|
+
}
|
|
11417
|
+
function hashHue(s) {
|
|
11418
|
+
let h = 2166136261;
|
|
11419
|
+
for (let i = 0; i < s.length; i++) {
|
|
11420
|
+
h ^= s.charCodeAt(i);
|
|
11421
|
+
h = Math.imul(h, 16777619);
|
|
11422
|
+
}
|
|
11423
|
+
return (h >>> 0) % 360;
|
|
11424
|
+
}
|
|
11425
|
+
function hslToRgb(h, sat, light) {
|
|
11426
|
+
const s = sat / 100;
|
|
11427
|
+
const l = light / 100;
|
|
11428
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
11429
|
+
const hp = h / 60;
|
|
11430
|
+
const x = c * (1 - Math.abs(hp % 2 - 1));
|
|
11431
|
+
let r = 0, g = 0, b = 0;
|
|
11432
|
+
if (hp < 1) [r, g, b] = [c, x, 0];
|
|
11433
|
+
else if (hp < 2) [r, g, b] = [x, c, 0];
|
|
11434
|
+
else if (hp < 3) [r, g, b] = [0, c, x];
|
|
11435
|
+
else if (hp < 4) [r, g, b] = [0, x, c];
|
|
11436
|
+
else if (hp < 5) [r, g, b] = [x, 0, c];
|
|
11437
|
+
else [r, g, b] = [c, 0, x];
|
|
11438
|
+
const m = l - c / 2;
|
|
11439
|
+
return [r + m, g + m, b + m];
|
|
11440
|
+
}
|
|
11441
|
+
function applyPalette(snap, seed) {
|
|
11442
|
+
const hue = hashHue(seed || "empty");
|
|
11443
|
+
const [pr, pg, pb] = hslToRgb(hue, 72, 48);
|
|
11444
|
+
const [sr, sg, sb] = hslToRgb((hue + 40) % 360, 55, 28);
|
|
11445
|
+
const [tr, tg, tb] = hslToRgb((hue + 20) % 360, 70, 72);
|
|
11446
|
+
snap.primaryColor = media.mediaVec3(pr, pg, pb);
|
|
11447
|
+
snap.secondaryColor = media.mediaVec3(sr, sg, sb);
|
|
11448
|
+
snap.tertiaryColor = media.mediaVec3(tr, tg, tb);
|
|
11449
|
+
snap.textColor = media.mediaVec3(0.98, 0.98, 1);
|
|
11450
|
+
snap.highContrastColor = media.mediaVec3(1, 1, 1);
|
|
11451
|
+
snap.hasThumbnail = !!seed;
|
|
11452
|
+
}
|
|
11453
|
+
function sampleArtworkPalette(img, w, h) {
|
|
11454
|
+
const c = document.createElement("canvas");
|
|
11455
|
+
c.width = 32;
|
|
11456
|
+
c.height = 32;
|
|
11457
|
+
const ctx = c.getContext("2d", { willReadFrequently: true });
|
|
11458
|
+
if (!ctx) return null;
|
|
11459
|
+
ctx.drawImage(img, 0, 0, w, h, 0, 0, 32, 32);
|
|
11460
|
+
const data = ctx.getImageData(0, 0, 32, 32).data;
|
|
11461
|
+
let r = 0, g = 0, b = 0, n = 0;
|
|
11462
|
+
let br = 0, bg = 0, bb = 0, best = -1;
|
|
11463
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
11464
|
+
const pr = data[i] / 255, pg = data[i + 1] / 255, pb = data[i + 2] / 255;
|
|
11465
|
+
r += pr;
|
|
11466
|
+
g += pg;
|
|
11467
|
+
b += pb;
|
|
11468
|
+
n++;
|
|
11469
|
+
const mx = Math.max(pr, pg, pb), mn = Math.min(pr, pg, pb);
|
|
11470
|
+
const sat = mx - mn;
|
|
11471
|
+
const lum = 0.2126 * pr + 0.7152 * pg + 0.0722 * pb;
|
|
11472
|
+
const score = sat * 1.4 + (lum > 0.15 && lum < 0.85 ? 0.3 : 0);
|
|
11473
|
+
if (score > best) {
|
|
11474
|
+
best = score;
|
|
11475
|
+
br = pr;
|
|
11476
|
+
bg = pg;
|
|
11477
|
+
bb = pb;
|
|
11478
|
+
}
|
|
11479
|
+
}
|
|
11480
|
+
if (!n) return null;
|
|
11481
|
+
const primary = [br, bg, bb];
|
|
11482
|
+
const secondary = [r / n * 0.55, g / n * 0.55, b / n * 0.55];
|
|
11483
|
+
const tertiary = [
|
|
11484
|
+
Math.min(1, primary[0] * 0.45 + 0.55),
|
|
11485
|
+
Math.min(1, primary[1] * 0.45 + 0.55),
|
|
11486
|
+
Math.min(1, primary[2] * 0.45 + 0.55)
|
|
11487
|
+
];
|
|
11488
|
+
return { primary, secondary, tertiary };
|
|
11489
|
+
}
|
|
11490
|
+
function rasterizeArtwork(img, srcW, srcH, size = 512) {
|
|
11491
|
+
const c = document.createElement("canvas");
|
|
11492
|
+
c.width = size;
|
|
11493
|
+
c.height = size;
|
|
11494
|
+
const ctx = c.getContext("2d");
|
|
11495
|
+
const scale = Math.max(size / Math.max(1, srcW), size / Math.max(1, srcH));
|
|
11496
|
+
const dw = srcW * scale;
|
|
11497
|
+
const dh = srcH * scale;
|
|
11498
|
+
ctx.fillStyle = "#000";
|
|
11499
|
+
ctx.fillRect(0, 0, size, size);
|
|
11500
|
+
ctx.drawImage(img, (size - dw) / 2, (size - dh) / 2, dw, dh);
|
|
11501
|
+
const id = ctx.getImageData(0, 0, size, size);
|
|
11502
|
+
return { width: size, height: size, rgba: new Uint8Array(id.data) };
|
|
11503
|
+
}
|
|
11504
|
+
function emptyMediaSnapshot() {
|
|
11505
|
+
return {
|
|
11506
|
+
hasMedia: false,
|
|
11507
|
+
state: MEDIA_PLAYBACK.STOPPED,
|
|
11508
|
+
title: "",
|
|
11509
|
+
artist: "",
|
|
11510
|
+
album: "",
|
|
11511
|
+
albumArtist: "",
|
|
11512
|
+
position: 0,
|
|
11513
|
+
duration: 0,
|
|
11514
|
+
hasThumbnail: false,
|
|
11515
|
+
primaryColor: media.mediaVec3(0, 0, 0),
|
|
11516
|
+
secondaryColor: media.mediaVec3(0, 0, 0),
|
|
11517
|
+
tertiaryColor: media.mediaVec3(0, 0, 0),
|
|
11518
|
+
textColor: media.mediaVec3(1, 1, 1),
|
|
11519
|
+
highContrastColor: media.mediaVec3(1, 1, 1),
|
|
11520
|
+
trackIndex: -1,
|
|
11521
|
+
lyrics: [],
|
|
11522
|
+
lyricLine: "",
|
|
11523
|
+
lyricIndex: -1
|
|
11524
|
+
};
|
|
11525
|
+
}
|
|
11526
|
+
async function openMicAnalyser() {
|
|
11527
|
+
if (!navigator.mediaDevices?.getUserMedia) return null;
|
|
11528
|
+
try {
|
|
11529
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
11530
|
+
audio: {
|
|
11531
|
+
echoCancellation: false,
|
|
11532
|
+
noiseSuppression: false,
|
|
11533
|
+
autoGainControl: false
|
|
11534
|
+
},
|
|
11535
|
+
video: false
|
|
11536
|
+
});
|
|
11537
|
+
const ctx = new AudioContext();
|
|
11538
|
+
const src = ctx.createMediaStreamSource(stream);
|
|
11539
|
+
const analyser = ctx.createAnalyser();
|
|
11540
|
+
analyser.fftSize = 2048;
|
|
11541
|
+
analyser.smoothingTimeConstant = 0.8;
|
|
11542
|
+
src.connect(analyser);
|
|
11543
|
+
if (ctx.state === "suspended") await ctx.resume().catch(() => {
|
|
11544
|
+
});
|
|
11545
|
+
return { ctx, stream, analyser, buf: new Uint8Array(analyser.frequencyBinCount) };
|
|
11546
|
+
} catch {
|
|
11547
|
+
return null;
|
|
11548
|
+
}
|
|
11549
|
+
}
|
|
11550
|
+
async function startLiveSystem(opts) {
|
|
11551
|
+
const origin = opts?.origin ?? (typeof location !== "undefined" ? location.origin : "");
|
|
11552
|
+
const onArtwork = opts?.onArtwork;
|
|
11553
|
+
const audioSnap = zeroBands();
|
|
11554
|
+
const mediaSnap = emptyMediaSnapshot();
|
|
11555
|
+
const winSnap = { app: "", title: "", url: "", index: 0 };
|
|
11556
|
+
let audioMode = "off";
|
|
11557
|
+
let mediaMode = "offline";
|
|
11558
|
+
let windowMode = "offline";
|
|
11559
|
+
let trackKey = "";
|
|
11560
|
+
let hasArtwork = false;
|
|
11561
|
+
let lastArtworkKey = "";
|
|
11562
|
+
const mic = await openMicAnalyser();
|
|
11563
|
+
if (mic) audioMode = "mic";
|
|
11564
|
+
else if (!navigator.mediaDevices?.getUserMedia) audioMode = "unavailable";
|
|
11565
|
+
else audioMode = "denied";
|
|
11566
|
+
let es = null;
|
|
11567
|
+
let pollTimer = null;
|
|
11568
|
+
let disposed = false;
|
|
11569
|
+
const requestArtwork = (key, title, artist) => {
|
|
11570
|
+
if (!origin || !onArtwork || !hasArtwork) return;
|
|
11571
|
+
if (lastArtworkKey === key) return;
|
|
11572
|
+
lastArtworkKey = key;
|
|
11573
|
+
onArtwork({
|
|
11574
|
+
url: `${origin}/api/system/artwork?k=${encodeURIComponent(key)}&_=${Date.now()}`,
|
|
11575
|
+
trackKey: key,
|
|
11576
|
+
title,
|
|
11577
|
+
artist
|
|
11578
|
+
});
|
|
11579
|
+
};
|
|
11580
|
+
const applyMediaPayload = (m) => {
|
|
11581
|
+
if (!m || !m.hasMedia) {
|
|
11582
|
+
mediaSnap.hasMedia = false;
|
|
11583
|
+
mediaSnap.state = MEDIA_PLAYBACK.STOPPED;
|
|
11584
|
+
mediaSnap.title = "";
|
|
11585
|
+
mediaSnap.artist = "";
|
|
11586
|
+
mediaSnap.album = "";
|
|
11587
|
+
mediaSnap.albumArtist = "";
|
|
11588
|
+
mediaSnap.position = 0;
|
|
11589
|
+
mediaSnap.duration = 0;
|
|
11590
|
+
mediaSnap.hasThumbnail = false;
|
|
11591
|
+
mediaSnap.trackIndex = -1;
|
|
11592
|
+
mediaMode = m ? "empty" : "offline";
|
|
11593
|
+
trackKey = "";
|
|
11594
|
+
hasArtwork = false;
|
|
11595
|
+
lastArtworkKey = "";
|
|
11596
|
+
return;
|
|
11597
|
+
}
|
|
11598
|
+
mediaMode = "live";
|
|
11599
|
+
mediaSnap.hasMedia = true;
|
|
11600
|
+
mediaSnap.state = Number(m.state) === 2 ? 2 : Number(m.state) === 1 ? 1 : 0;
|
|
11601
|
+
mediaSnap.title = String(m.title ?? "");
|
|
11602
|
+
mediaSnap.artist = String(m.artist ?? "");
|
|
11603
|
+
mediaSnap.album = String(m.album ?? "");
|
|
11604
|
+
mediaSnap.albumArtist = String(m.albumArtist ?? m.artist ?? "");
|
|
11605
|
+
mediaSnap.position = Number(m.position) || 0;
|
|
11606
|
+
mediaSnap.duration = Number(m.duration) || 0;
|
|
11607
|
+
hasArtwork = m.hasArtwork === true;
|
|
11608
|
+
const key = `${mediaSnap.title}|${mediaSnap.artist}|${mediaSnap.album}`;
|
|
11609
|
+
if (key !== trackKey) {
|
|
11610
|
+
trackKey = key;
|
|
11611
|
+
lastArtworkKey = "";
|
|
11612
|
+
mediaSnap.trackIndex = mediaSnap.trackIndex + 1 | 0;
|
|
11613
|
+
applyPalette(mediaSnap, key);
|
|
11614
|
+
requestArtwork(key, mediaSnap.title, mediaSnap.artist);
|
|
11615
|
+
} else {
|
|
11616
|
+
requestArtwork(key, mediaSnap.title, mediaSnap.artist);
|
|
11617
|
+
}
|
|
11618
|
+
};
|
|
11619
|
+
const applyWindowPayload = (w) => {
|
|
11620
|
+
if (!w) {
|
|
11621
|
+
windowMode = "offline";
|
|
11622
|
+
return;
|
|
11623
|
+
}
|
|
11624
|
+
winSnap.app = String(w.app ?? "");
|
|
11625
|
+
winSnap.title = String(w.title ?? "");
|
|
11626
|
+
winSnap.url = String(w.url ?? "");
|
|
11627
|
+
windowMode = winSnap.app || winSnap.title ? "live" : "empty";
|
|
11628
|
+
};
|
|
11629
|
+
const pollOnce = async () => {
|
|
11630
|
+
if (!origin || disposed) return;
|
|
11631
|
+
try {
|
|
11632
|
+
const [mr, wr] = await Promise.all([
|
|
11633
|
+
fetch(`${origin}/api/system/media`, { cache: "no-store" }),
|
|
11634
|
+
fetch(`${origin}/api/system/window`, { cache: "no-store" })
|
|
11635
|
+
]);
|
|
11636
|
+
if (mr.ok) {
|
|
11637
|
+
const j = await mr.json();
|
|
11638
|
+
applyMediaPayload(j);
|
|
11639
|
+
} else {
|
|
11640
|
+
mediaMode = "offline";
|
|
11641
|
+
}
|
|
11642
|
+
if (wr.ok) {
|
|
11643
|
+
applyWindowPayload(await wr.json());
|
|
11644
|
+
}
|
|
11645
|
+
} catch {
|
|
11646
|
+
mediaMode = mediaMode === "live" ? "live" : "offline";
|
|
11647
|
+
windowMode = windowMode === "live" ? "live" : "offline";
|
|
11648
|
+
}
|
|
11649
|
+
};
|
|
11650
|
+
if (origin) {
|
|
11651
|
+
await pollOnce();
|
|
11652
|
+
pollTimer = setInterval(() => void pollOnce(), 1e3);
|
|
11653
|
+
try {
|
|
11654
|
+
es = new EventSource(`${origin}/api/system/stream`);
|
|
11655
|
+
es.onmessage = (ev) => {
|
|
11656
|
+
if (disposed) return;
|
|
11657
|
+
try {
|
|
11658
|
+
const data = JSON.parse(ev.data);
|
|
11659
|
+
applyMediaPayload(data.media);
|
|
11660
|
+
applyWindowPayload(data.window);
|
|
11661
|
+
} catch {
|
|
11662
|
+
}
|
|
11663
|
+
};
|
|
11664
|
+
} catch {
|
|
11665
|
+
}
|
|
11666
|
+
}
|
|
11667
|
+
const postControl = (action) => {
|
|
11668
|
+
if (!origin || disposed) return;
|
|
11669
|
+
void fetch(`${origin}/api/system/media-control`, {
|
|
11670
|
+
method: "POST",
|
|
11671
|
+
headers: { "Content-Type": "application/json" },
|
|
11672
|
+
body: JSON.stringify({ action })
|
|
11673
|
+
}).then(async (r) => {
|
|
11674
|
+
if (!r.ok) return null;
|
|
11675
|
+
return r.json();
|
|
11676
|
+
}).then((j) => {
|
|
11677
|
+
if (j && typeof j === "object") applyMediaPayload(j);
|
|
11678
|
+
void pollOnce();
|
|
11679
|
+
}).catch(() => {
|
|
11680
|
+
void pollOnce();
|
|
11681
|
+
});
|
|
11682
|
+
};
|
|
11683
|
+
return {
|
|
11684
|
+
audio: {
|
|
11685
|
+
snapshot: audioSnap,
|
|
11686
|
+
pump: () => {
|
|
11687
|
+
if (!mic || disposed) {
|
|
11688
|
+
audioSnap.level = 0;
|
|
11689
|
+
audioSnap.silent = true;
|
|
11690
|
+
return;
|
|
11691
|
+
}
|
|
11692
|
+
mic.analyser.getByteFrequencyData(mic.buf);
|
|
11693
|
+
fillFromByteFreq(audioSnap, mic.buf, mic.ctx.sampleRate || 48e3);
|
|
11694
|
+
}
|
|
11695
|
+
},
|
|
11696
|
+
media: {
|
|
11697
|
+
snapshot: mediaSnap,
|
|
11698
|
+
pump: () => {
|
|
11699
|
+
},
|
|
11700
|
+
skipNext: () => postControl("skipNext"),
|
|
11701
|
+
skipPrevious: () => postControl("skipPrevious"),
|
|
11702
|
+
play: () => postControl("play"),
|
|
11703
|
+
pause: () => postControl("pause"),
|
|
11704
|
+
playPause: () => postControl("playPause")
|
|
11705
|
+
},
|
|
11706
|
+
windowTitle: {
|
|
11707
|
+
snapshot: winSnap,
|
|
11708
|
+
pump: () => {
|
|
11709
|
+
}
|
|
11710
|
+
},
|
|
11711
|
+
status: () => ({
|
|
11712
|
+
audio: audioMode,
|
|
11713
|
+
media: mediaMode,
|
|
11714
|
+
window: windowMode,
|
|
11715
|
+
title: mediaSnap.title,
|
|
11716
|
+
artist: mediaSnap.artist,
|
|
11717
|
+
app: winSnap.app,
|
|
11718
|
+
windowTitle: winSnap.title,
|
|
11719
|
+
hasArtwork
|
|
11720
|
+
}),
|
|
11721
|
+
dispose: () => {
|
|
11722
|
+
disposed = true;
|
|
11723
|
+
if (pollTimer) {
|
|
11724
|
+
clearInterval(pollTimer);
|
|
11725
|
+
pollTimer = null;
|
|
11726
|
+
}
|
|
11727
|
+
try {
|
|
11728
|
+
es?.close();
|
|
11729
|
+
} catch {
|
|
11730
|
+
}
|
|
11731
|
+
es = null;
|
|
11732
|
+
if (mic) {
|
|
11733
|
+
try {
|
|
11734
|
+
mic.stream.getTracks().forEach((t) => t.stop());
|
|
11735
|
+
void mic.ctx.close();
|
|
11736
|
+
} catch {
|
|
11737
|
+
}
|
|
11738
|
+
}
|
|
11739
|
+
}
|
|
11740
|
+
};
|
|
11741
|
+
}
|
|
11148
11742
|
const WE_SHADER_HEADERS = {
|
|
11149
11743
|
"common.h": `// WE common.h(重建子集,供 we-scene 浏览器渲染)
|
|
11150
11744
|
#define M_PI 3.14159265359
|
|
@@ -11417,10 +12011,18 @@ mat3 squareToQuad(vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
|
|
|
11417
12011
|
float d = p1.y - p0.y + g * p1.y;
|
|
11418
12012
|
float e = p3.y - p0.y + h * p3.y;
|
|
11419
12013
|
float f = p0.y;
|
|
11420
|
-
//
|
|
11421
|
-
|
|
11422
|
-
|
|
11423
|
-
|
|
12014
|
+
// 调用点一律是 mul(vec3(uv,1), inverse(本函数结果)),hlsl2glsl 把它转写成
|
|
12015
|
+
// transpose(xform) * vec3(uv,1)。要让屏幕点 s 得到 texCoord = S⁻¹·s(S 为
|
|
12016
|
+
// Heckbert 正向矩阵 [[a,b,c],[d,e,f],[g,h,1]],单位方→四边形),必须
|
|
12017
|
+
// xform = inverse(本函数结果) 满足 transpose(xform)·s = S⁻¹·s,
|
|
12018
|
+
// 即本函数返回 S 的**转置**:mat3 列主序构造为 (a,d,g)(b,e,h)(c,f,1) 的转置
|
|
12019
|
+
// = (a,b,c)(d,e,f)(g,h,1)。排布差一个转置,perspective/水波等全部错位——
|
|
12020
|
+
// 3174556087 的音谱柱被贴到窗户侧边竖排(应为贴下窗沿横排)实测确认。
|
|
12021
|
+
// 2026-09-05 数值模拟:旧排布 transpose(S⁻¹)·corner 与正确 S⁻¹·corner 逐项不同,
|
|
12022
|
+
// 可见区塌缩成一条斜带;新排布后中心 (0.5,0.5) → (0.478,0.511) ∈ [0,1]²。
|
|
12023
|
+
return mat3(a, b, c,
|
|
12024
|
+
d, e, f,
|
|
12025
|
+
g, h, 1.0);
|
|
11424
12026
|
}
|
|
11425
12027
|
`,
|
|
11426
12028
|
// WE common_blur.h(重建)。blurNa 的权重不是估算的 —— 壁纸 1444077782 里存着
|
|
@@ -11550,6 +12152,66 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
11550
12152
|
"common_vertex.h": `// WE common_vertex.h(重建:空占位,见 headers.ts 注释)
|
|
11551
12153
|
`
|
|
11552
12154
|
};
|
|
12155
|
+
function sanitizeFontForBrowser(src) {
|
|
12156
|
+
if (!src || src.length < 12) return src;
|
|
12157
|
+
const b0 = src[0], b1 = src[1], b2 = src[2], b3 = src[3];
|
|
12158
|
+
const isTtf = b0 === 0 && b1 === 1 && b2 === 0 && b3 === 0;
|
|
12159
|
+
const isOtto = b0 === 79 && b1 === 84 && b2 === 84 && b3 === 79;
|
|
12160
|
+
if (!isTtf && !isOtto) return src;
|
|
12161
|
+
const out = Uint8Array.from(src);
|
|
12162
|
+
const dv = new DataView(out.buffer, out.byteOffset, out.byteLength);
|
|
12163
|
+
const numTables = dv.getUint16(4);
|
|
12164
|
+
if (numTables <= 0 || 12 + numTables * 16 > out.length) return src;
|
|
12165
|
+
let cmapEntry = -1;
|
|
12166
|
+
let cmapOffset = 0;
|
|
12167
|
+
let cmapLength = 0;
|
|
12168
|
+
for (let i = 0; i < numTables; i++) {
|
|
12169
|
+
const e = 12 + i * 16;
|
|
12170
|
+
const tag = String.fromCharCode(out[e], out[e + 1], out[e + 2], out[e + 3]);
|
|
12171
|
+
if (tag === "cmap") {
|
|
12172
|
+
cmapEntry = e;
|
|
12173
|
+
cmapOffset = dv.getUint32(e + 8);
|
|
12174
|
+
cmapLength = dv.getUint32(e + 12);
|
|
12175
|
+
break;
|
|
12176
|
+
}
|
|
12177
|
+
}
|
|
12178
|
+
if (cmapEntry < 0 || cmapOffset + cmapLength > out.length) return src;
|
|
12179
|
+
let changed = false;
|
|
12180
|
+
const numEnc = dv.getUint16(cmapOffset + 2);
|
|
12181
|
+
for (let i = 0; i < numEnc; i++) {
|
|
12182
|
+
const rec = cmapOffset + 4 + i * 8;
|
|
12183
|
+
const soff = dv.getUint32(rec + 4);
|
|
12184
|
+
const abs = cmapOffset + soff;
|
|
12185
|
+
if (abs + 14 > out.length) continue;
|
|
12186
|
+
if (dv.getUint16(abs) !== 4) continue;
|
|
12187
|
+
const segCountX2 = dv.getUint16(abs + 6);
|
|
12188
|
+
const segCount = segCountX2 >>> 1;
|
|
12189
|
+
if (segCount < 1) continue;
|
|
12190
|
+
const expSearch = 2 * Math.pow(2, Math.floor(Math.log2(segCount)));
|
|
12191
|
+
const expSel = Math.floor(Math.log2(segCount));
|
|
12192
|
+
const expShift = segCountX2 - expSearch;
|
|
12193
|
+
const curSearch = dv.getUint16(abs + 8);
|
|
12194
|
+
const curSel = dv.getUint16(abs + 10);
|
|
12195
|
+
const curShift = dv.getUint16(abs + 12);
|
|
12196
|
+
if (curSearch === expSearch && curSel === expSel && curShift === expShift) continue;
|
|
12197
|
+
dv.setUint16(abs + 8, expSearch);
|
|
12198
|
+
dv.setUint16(abs + 10, expSel);
|
|
12199
|
+
dv.setUint16(abs + 12, expShift);
|
|
12200
|
+
changed = true;
|
|
12201
|
+
}
|
|
12202
|
+
if (!changed) return src;
|
|
12203
|
+
let sum = 0;
|
|
12204
|
+
const end = cmapOffset + cmapLength;
|
|
12205
|
+
for (let p = cmapOffset; p < end; p += 4) {
|
|
12206
|
+
const b02 = out[p] || 0;
|
|
12207
|
+
const b12 = p + 1 < end ? out[p + 1] : 0;
|
|
12208
|
+
const b22 = p + 2 < end ? out[p + 2] : 0;
|
|
12209
|
+
const b32 = p + 3 < end ? out[p + 3] : 0;
|
|
12210
|
+
sum = sum + (b02 << 24 | b12 << 16 | b22 << 8 | b32) >>> 0;
|
|
12211
|
+
}
|
|
12212
|
+
dv.setUint32(cmapEntry + 4, sum);
|
|
12213
|
+
return out;
|
|
12214
|
+
}
|
|
11553
12215
|
const SYSTEM_FONT_FAMILIES = {
|
|
11554
12216
|
systemfont_segoe: "'Segoe UI', 'Helvetica Neue', Arial, sans-serif",
|
|
11555
12217
|
systemfont_arial: "Arial, 'Helvetica Neue', sans-serif",
|
|
@@ -11751,6 +12413,22 @@ function mountScene(rt, cfg) {
|
|
|
11751
12413
|
if (disposed) return;
|
|
11752
12414
|
const supportsAudioProcessing = project?.general?.supportsaudioprocessing !== false;
|
|
11753
12415
|
const simAudio = createSimulatedAudio();
|
|
12416
|
+
const simMedia = media.createSimulatedMedia();
|
|
12417
|
+
const simWindow = system.createSimulatedWindowTitle();
|
|
12418
|
+
let live = null;
|
|
12419
|
+
const liveHold = {
|
|
12420
|
+
mediaDriver: null,
|
|
12421
|
+
lastSnap: {
|
|
12422
|
+
get: () => null,
|
|
12423
|
+
setHasThumbnail: () => {
|
|
12424
|
+
}
|
|
12425
|
+
}
|
|
12426
|
+
};
|
|
12427
|
+
const audioDriverRef = {
|
|
12428
|
+
current: null
|
|
12429
|
+
};
|
|
12430
|
+
let mediaDriver = simMedia;
|
|
12431
|
+
let windowDriver = simWindow;
|
|
11754
12432
|
const audioSim = { enabled: supportsAudioProcessing };
|
|
11755
12433
|
const zero = (n) => new Float32Array(n);
|
|
11756
12434
|
const SILENT_AUDIO = {
|
|
@@ -11763,32 +12441,45 @@ function mountScene(rt, cfg) {
|
|
|
11763
12441
|
level: 0,
|
|
11764
12442
|
silent: true
|
|
11765
12443
|
};
|
|
11766
|
-
renderer.setAudioProvider(() =>
|
|
12444
|
+
renderer.setAudioProvider(() => {
|
|
12445
|
+
if (!audioSim.enabled) return SILENT_AUDIO;
|
|
12446
|
+
return audioDriverRef.current ? audioDriverRef.current.snapshot : simAudio.snapshot;
|
|
12447
|
+
});
|
|
11767
12448
|
const audioViews = /* @__PURE__ */ new Map();
|
|
11768
12449
|
window.__audioStats = () => ({
|
|
11769
12450
|
enabled: audioSim.enabled,
|
|
11770
|
-
|
|
11771
|
-
|
|
11772
|
-
|
|
12451
|
+
live: !!audioDriverRef.current,
|
|
12452
|
+
level: audioSim.enabled ? Math.round((audioDriverRef.current ? audioDriverRef.current.snapshot : simAudio.snapshot).level * 1e3) / 1e3 : 0,
|
|
12453
|
+
silent: audioSim.enabled ? (audioDriverRef.current ? audioDriverRef.current.snapshot : simAudio.snapshot).silent : true,
|
|
12454
|
+
bass: audioSim.enabled ? Math.round((audioDriverRef.current ? audioDriverRef.current.snapshot : simAudio.snapshot).left64[2] * 1e3) / 1e3 : 0
|
|
11773
12455
|
});
|
|
11774
12456
|
window.__audioMute = (on) => {
|
|
11775
12457
|
audioSim.enabled = !on;
|
|
11776
12458
|
return audioSim.enabled;
|
|
11777
12459
|
};
|
|
11778
|
-
reportDiag(
|
|
11779
|
-
|
|
11780
|
-
|
|
12460
|
+
reportDiag(
|
|
12461
|
+
rt,
|
|
12462
|
+
cfg,
|
|
12463
|
+
`audio: ${audioDriverRef.current ? "live mic" : "simulated"} stream, supportsaudioprocessing=${supportsAudioProcessing}`
|
|
12464
|
+
);
|
|
11781
12465
|
const shortcuts = system.createShortcutHandler((name) => {
|
|
11782
12466
|
reportDiag(rt, cfg, `openUserShortcut: ${name}`);
|
|
11783
12467
|
});
|
|
11784
12468
|
const mediaSim = { enabled: true, override: null };
|
|
11785
12469
|
const mediaHooks = [];
|
|
11786
12470
|
let lastMediaSnap = null;
|
|
12471
|
+
liveHold.lastSnap = {
|
|
12472
|
+
get: () => lastMediaSnap,
|
|
12473
|
+
setHasThumbnail: (v) => {
|
|
12474
|
+
if (lastMediaSnap) lastMediaSnap.hasThumbnail = v;
|
|
12475
|
+
}
|
|
12476
|
+
};
|
|
12477
|
+
const mediaSnapshot = () => mediaDriver.snapshot;
|
|
11787
12478
|
const registerMediaHook = (sb) => {
|
|
11788
12479
|
if (!sb || !sb.hasMediaHook || mediaHooks.includes(sb)) return;
|
|
11789
12480
|
mediaHooks.push(sb);
|
|
11790
|
-
if (!
|
|
11791
|
-
for (const { name, event } of media.diffMediaEvents(null,
|
|
12481
|
+
if (!mediaSnapshot().hasMedia) return;
|
|
12482
|
+
for (const { name, event } of media.diffMediaEvents(null, mediaSnapshot())) {
|
|
11792
12483
|
try {
|
|
11793
12484
|
sb.callMedia(name, event);
|
|
11794
12485
|
} catch {
|
|
@@ -11797,26 +12488,27 @@ function mountScene(rt, cfg) {
|
|
|
11797
12488
|
};
|
|
11798
12489
|
window.__mediaStats = () => ({
|
|
11799
12490
|
enabled: mediaSim.enabled,
|
|
12491
|
+
live: !!live,
|
|
11800
12492
|
hooks: mediaHooks.length,
|
|
11801
|
-
title:
|
|
11802
|
-
artist:
|
|
11803
|
-
album:
|
|
11804
|
-
state:
|
|
11805
|
-
position: Math.round(
|
|
11806
|
-
duration:
|
|
11807
|
-
hasThumbnail:
|
|
11808
|
-
lyric:
|
|
11809
|
-
primaryColor:
|
|
12493
|
+
title: mediaSnapshot().title,
|
|
12494
|
+
artist: mediaSnapshot().artist,
|
|
12495
|
+
album: mediaSnapshot().album,
|
|
12496
|
+
state: mediaSnapshot().state,
|
|
12497
|
+
position: Math.round(mediaSnapshot().position),
|
|
12498
|
+
duration: mediaSnapshot().duration,
|
|
12499
|
+
hasThumbnail: mediaSnapshot().hasThumbnail,
|
|
12500
|
+
lyric: mediaSnapshot().lyricLine,
|
|
12501
|
+
primaryColor: mediaSnapshot().primaryColor ? [mediaSnapshot().primaryColor.x, mediaSnapshot().primaryColor.y, mediaSnapshot().primaryColor.z] : null
|
|
11810
12502
|
});
|
|
11811
12503
|
window.__mediaSet = (patch) => {
|
|
11812
|
-
Object.assign(
|
|
11813
|
-
const evts = media.diffMediaEvents(lastMediaSnap,
|
|
12504
|
+
Object.assign(mediaSnapshot(), patch || {});
|
|
12505
|
+
const evts = media.diffMediaEvents(lastMediaSnap, mediaSnapshot());
|
|
11814
12506
|
for (const { name, event } of evts) for (const sb of mediaHooks) sb.callMedia(name, event);
|
|
11815
|
-
lastMediaSnap = media.cloneMediaSnapshot(
|
|
12507
|
+
lastMediaSnap = media.cloneMediaSnapshot(mediaSnapshot());
|
|
11816
12508
|
return window.__mediaStats();
|
|
11817
12509
|
};
|
|
11818
12510
|
const dispatchMediaNow = () => {
|
|
11819
|
-
const evts = media.diffMediaEvents(lastMediaSnap,
|
|
12511
|
+
const evts = media.diffMediaEvents(lastMediaSnap, mediaSnapshot());
|
|
11820
12512
|
for (const { name, event } of evts) {
|
|
11821
12513
|
for (const sb of mediaHooks) {
|
|
11822
12514
|
try {
|
|
@@ -11825,42 +12517,50 @@ function mountScene(rt, cfg) {
|
|
|
11825
12517
|
}
|
|
11826
12518
|
}
|
|
11827
12519
|
}
|
|
11828
|
-
lastMediaSnap = media.cloneMediaSnapshot(
|
|
12520
|
+
lastMediaSnap = media.cloneMediaSnapshot(mediaSnapshot());
|
|
11829
12521
|
};
|
|
11830
12522
|
const mediaControl = {
|
|
11831
|
-
snapshot
|
|
12523
|
+
get snapshot() {
|
|
12524
|
+
return mediaSnapshot();
|
|
12525
|
+
},
|
|
11832
12526
|
skipNext: () => {
|
|
11833
|
-
|
|
12527
|
+
mediaDriver.skipNext();
|
|
11834
12528
|
dispatchMediaNow();
|
|
11835
|
-
return
|
|
12529
|
+
return mediaSnapshot();
|
|
11836
12530
|
},
|
|
11837
12531
|
skipPrevious: () => {
|
|
11838
|
-
|
|
12532
|
+
mediaDriver.skipPrevious();
|
|
11839
12533
|
dispatchMediaNow();
|
|
11840
|
-
return
|
|
12534
|
+
return mediaSnapshot();
|
|
11841
12535
|
},
|
|
11842
12536
|
play: () => {
|
|
11843
|
-
|
|
12537
|
+
mediaDriver.play();
|
|
11844
12538
|
dispatchMediaNow();
|
|
11845
|
-
return
|
|
12539
|
+
return mediaSnapshot();
|
|
11846
12540
|
},
|
|
11847
12541
|
pause: () => {
|
|
11848
|
-
|
|
12542
|
+
mediaDriver.pause();
|
|
11849
12543
|
dispatchMediaNow();
|
|
11850
|
-
return
|
|
12544
|
+
return mediaSnapshot();
|
|
11851
12545
|
},
|
|
11852
12546
|
playPause: () => {
|
|
11853
|
-
|
|
12547
|
+
mediaDriver.playPause();
|
|
11854
12548
|
dispatchMediaNow();
|
|
11855
|
-
return
|
|
12549
|
+
return mediaSnapshot();
|
|
11856
12550
|
}
|
|
11857
12551
|
};
|
|
11858
12552
|
window.__mediaControl = mediaControl;
|
|
11859
12553
|
window.__system = {
|
|
11860
12554
|
media: mediaControl,
|
|
11861
|
-
windowTitle:
|
|
11862
|
-
shortcuts: shortcuts.last
|
|
12555
|
+
windowTitle: windowDriver.snapshot,
|
|
12556
|
+
shortcuts: shortcuts.last,
|
|
12557
|
+
live: null
|
|
11863
12558
|
};
|
|
12559
|
+
window.__liveSystem = () => ({
|
|
12560
|
+
audio: "off",
|
|
12561
|
+
media: "offline",
|
|
12562
|
+
window: "offline"
|
|
12563
|
+
});
|
|
11864
12564
|
const pointerSrc = pointerLib.createPointerSource(
|
|
11865
12565
|
cfg.canvas ? {
|
|
11866
12566
|
target: cfg.canvas,
|
|
@@ -11964,6 +12664,118 @@ function mountScene(rt, cfg) {
|
|
|
11964
12664
|
textures.set("$mediaPreviousThumbnail", mkThumb(tracks[tracks.length - 1]));
|
|
11965
12665
|
}
|
|
11966
12666
|
}
|
|
12667
|
+
if (cfg.liveSystem) {
|
|
12668
|
+
try {
|
|
12669
|
+
const uploadLiveArtwork = async (info) => {
|
|
12670
|
+
try {
|
|
12671
|
+
const res = await fetch(info.url, { cache: "no-store" });
|
|
12672
|
+
if (!res.ok) return;
|
|
12673
|
+
const blob = await res.blob();
|
|
12674
|
+
const bmp = await createImageBitmap(blob);
|
|
12675
|
+
const raster = rasterizeArtwork(bmp, bmp.width, bmp.height, 512);
|
|
12676
|
+
const palette = sampleArtworkPalette(bmp, bmp.width, bmp.height);
|
|
12677
|
+
bmp.close?.();
|
|
12678
|
+
const cur = textures.get("$mediaThumbnail");
|
|
12679
|
+
if (cur) textures.set("$mediaPreviousThumbnail", cur);
|
|
12680
|
+
const gl = renderer.gl;
|
|
12681
|
+
const existing = textures.get("$mediaThumbnail");
|
|
12682
|
+
if (existing?.glTex) {
|
|
12683
|
+
gl.bindTexture(gl.TEXTURE_2D, existing.glTex);
|
|
12684
|
+
gl.texImage2D(
|
|
12685
|
+
gl.TEXTURE_2D,
|
|
12686
|
+
0,
|
|
12687
|
+
gl.RGBA,
|
|
12688
|
+
raster.width,
|
|
12689
|
+
raster.height,
|
|
12690
|
+
0,
|
|
12691
|
+
gl.RGBA,
|
|
12692
|
+
gl.UNSIGNED_BYTE,
|
|
12693
|
+
raster.rgba
|
|
12694
|
+
);
|
|
12695
|
+
existing.width = raster.width;
|
|
12696
|
+
existing.height = raster.height;
|
|
12697
|
+
existing.mips = [raster];
|
|
12698
|
+
} else {
|
|
12699
|
+
textures.set("$mediaThumbnail", {
|
|
12700
|
+
glTex: rnd.makeTextureMip(gl, [raster], false),
|
|
12701
|
+
width: raster.width,
|
|
12702
|
+
height: raster.height,
|
|
12703
|
+
rg88: false,
|
|
12704
|
+
mips: [raster],
|
|
12705
|
+
generated: true
|
|
12706
|
+
});
|
|
12707
|
+
}
|
|
12708
|
+
const snap = mediaDriver.snapshot;
|
|
12709
|
+
if (palette) {
|
|
12710
|
+
snap.primaryColor = media.mediaVec3(...palette.primary);
|
|
12711
|
+
snap.secondaryColor = media.mediaVec3(...palette.secondary);
|
|
12712
|
+
snap.tertiaryColor = media.mediaVec3(...palette.tertiary);
|
|
12713
|
+
snap.textColor = media.mediaVec3(0.98, 0.98, 1);
|
|
12714
|
+
snap.highContrastColor = media.mediaVec3(1, 1, 1);
|
|
12715
|
+
}
|
|
12716
|
+
snap.hasThumbnail = true;
|
|
12717
|
+
liveHold.lastSnap.setHasThumbnail(false);
|
|
12718
|
+
reportDiag(rt, cfg, `liveSystem: artwork ${info.title || info.trackKey}`);
|
|
12719
|
+
} catch (e) {
|
|
12720
|
+
reportDiag(
|
|
12721
|
+
rt,
|
|
12722
|
+
cfg,
|
|
12723
|
+
`liveSystem: artwork 失败 (${e instanceof Error ? e.message : e})`
|
|
12724
|
+
);
|
|
12725
|
+
}
|
|
12726
|
+
};
|
|
12727
|
+
live = await startLiveSystem({
|
|
12728
|
+
origin: location.origin,
|
|
12729
|
+
onArtwork: (info) => {
|
|
12730
|
+
void uploadLiveArtwork(info);
|
|
12731
|
+
}
|
|
12732
|
+
});
|
|
12733
|
+
mediaDriver = live.media;
|
|
12734
|
+
windowDriver = live.windowTitle;
|
|
12735
|
+
liveHold.mediaDriver = live.media;
|
|
12736
|
+
if (live.status().audio === "mic") audioDriverRef.current = live.audio;
|
|
12737
|
+
if (mediaDriver.snapshot.hasMedia) {
|
|
12738
|
+
for (const { name, event } of media.diffMediaEvents(null, mediaDriver.snapshot)) {
|
|
12739
|
+
for (const sb of mediaHooks) {
|
|
12740
|
+
try {
|
|
12741
|
+
sb.callMedia(name, event);
|
|
12742
|
+
} catch {
|
|
12743
|
+
}
|
|
12744
|
+
}
|
|
12745
|
+
}
|
|
12746
|
+
lastMediaSnap = media.cloneMediaSnapshot(mediaDriver.snapshot);
|
|
12747
|
+
}
|
|
12748
|
+
const st = live.status();
|
|
12749
|
+
reportDiag(
|
|
12750
|
+
rt,
|
|
12751
|
+
cfg,
|
|
12752
|
+
`liveSystem: audio=${st.audio} media=${st.media} window=${st.window}` + (st.title ? ` title="${st.title}"` : "") + (st.hasArtwork ? " artwork=1" : "")
|
|
12753
|
+
);
|
|
12754
|
+
reportDiag(
|
|
12755
|
+
rt,
|
|
12756
|
+
cfg,
|
|
12757
|
+
`audio: ${audioDriverRef.current ? "live mic" : "simulated"} stream, supportsaudioprocessing=${supportsAudioProcessing}`
|
|
12758
|
+
);
|
|
12759
|
+
window.__system = {
|
|
12760
|
+
media: mediaControl,
|
|
12761
|
+
windowTitle: windowDriver.snapshot,
|
|
12762
|
+
shortcuts: shortcuts.last,
|
|
12763
|
+
live: () => live.status()
|
|
12764
|
+
};
|
|
12765
|
+
window.__liveSystem = () => live.status();
|
|
12766
|
+
} catch (e) {
|
|
12767
|
+
reportDiag(rt, cfg, `liveSystem: 启动失败,回退模拟源 (${e instanceof Error ? e.message : e})`);
|
|
12768
|
+
live = null;
|
|
12769
|
+
}
|
|
12770
|
+
}
|
|
12771
|
+
{
|
|
12772
|
+
const prevCleanup = particleCleanup;
|
|
12773
|
+
particleCleanup = () => {
|
|
12774
|
+
prevCleanup?.();
|
|
12775
|
+
live?.dispose();
|
|
12776
|
+
live = null;
|
|
12777
|
+
};
|
|
12778
|
+
}
|
|
11967
12779
|
const texInflight = /* @__PURE__ */ new Map();
|
|
11968
12780
|
const loadTexInner = async (name) => {
|
|
11969
12781
|
if (textures.has(name)) return textures.get(name);
|
|
@@ -12155,6 +12967,13 @@ function mountScene(rt, cfg) {
|
|
|
12155
12967
|
model = JSON.parse(readText(modelEntry));
|
|
12156
12968
|
}
|
|
12157
12969
|
scn.applySolidFromModel(layer, model);
|
|
12970
|
+
const instUt = layer.srcObject?.instance?.usertextures;
|
|
12971
|
+
const instUtName = instUt?.[0] && typeof instUt[0].name === "string" && instUt[0].name.startsWith("$") ? instUt[0].name : null;
|
|
12972
|
+
const instBoundTex = instUtName && textures.has(instUtName) ? instUtName : null;
|
|
12973
|
+
if (instBoundTex) {
|
|
12974
|
+
layer.textureName = instBoundTex;
|
|
12975
|
+
layer.solid = false;
|
|
12976
|
+
}
|
|
12158
12977
|
if (model && typeof model === "object" && "width" in model && "height" in model) {
|
|
12159
12978
|
const m = model;
|
|
12160
12979
|
if ((layer.size?.[0] || 0) === 0 && (layer.size?.[1] || 0) === 0 && m.width > 0 && m.height > 0) {
|
|
@@ -12195,7 +13014,7 @@ function mountScene(rt, cfg) {
|
|
|
12195
13014
|
texJobs.push(
|
|
12196
13015
|
loadTex(tn).then((entry) => {
|
|
12197
13016
|
if (!entry) return;
|
|
12198
|
-
if (si === 0) {
|
|
13017
|
+
if (si === 0 && !instBoundTex) {
|
|
12199
13018
|
layer.textureName = tn;
|
|
12200
13019
|
loadedTex++;
|
|
12201
13020
|
if (entry.videoCtl) layer.videoCtl = entry.videoCtl;
|
|
@@ -12266,7 +13085,10 @@ function mountScene(rt, cfg) {
|
|
|
12266
13085
|
height: gen.height,
|
|
12267
13086
|
rg88: false,
|
|
12268
13087
|
mips: [gen],
|
|
12269
|
-
generated: true
|
|
13088
|
+
generated: true,
|
|
13089
|
+
// 内置贴图的帧表(rain1/rain2 的 1×4 图集):randomframe 预设依赖它
|
|
13090
|
+
// 随机取帧,缺了就整图采样画出超长丝(1823900922)。
|
|
13091
|
+
frames: ptex.builtinParticleFrames(name) ?? void 0
|
|
12270
13092
|
};
|
|
12271
13093
|
textures.set(name, entry);
|
|
12272
13094
|
builtinTexCount++;
|
|
@@ -12472,8 +13294,8 @@ function mountScene(rt, cfg) {
|
|
|
12472
13294
|
if (particleDiagFrame < 2) {
|
|
12473
13295
|
particleDiagFrame++;
|
|
12474
13296
|
if (particleDiagFrame === 2) {
|
|
12475
|
-
const
|
|
12476
|
-
reportDiag(rt, cfg, `particles live: ${
|
|
13297
|
+
const live2 = particleSystems.reduce((s, ps) => s + ps.liveCount(), 0);
|
|
13298
|
+
reportDiag(rt, cfg, `particles live: ${live2} across ${particleSystems.length} systems`);
|
|
12477
13299
|
}
|
|
12478
13300
|
}
|
|
12479
13301
|
},
|
|
@@ -12492,7 +13314,7 @@ function mountScene(rt, cfg) {
|
|
|
12492
13314
|
`particles: ${particleSystems.length} systems, ${builtinTexCount} builtin tex generated`
|
|
12493
13315
|
);
|
|
12494
13316
|
window.__particleStats = () => particleSystems.map((ps) => {
|
|
12495
|
-
let
|
|
13317
|
+
let live2 = 0;
|
|
12496
13318
|
let minX = Infinity;
|
|
12497
13319
|
let maxX = -Infinity;
|
|
12498
13320
|
let minY = Infinity;
|
|
@@ -12501,7 +13323,7 @@ function mountScene(rt, cfg) {
|
|
|
12501
13323
|
let maxS = -Infinity;
|
|
12502
13324
|
for (const p of ps.pool) {
|
|
12503
13325
|
if (!p.alive) continue;
|
|
12504
|
-
|
|
13326
|
+
live2++;
|
|
12505
13327
|
const px = ps.originX + p.x * ps.scaleX;
|
|
12506
13328
|
const py = ps.originY + p.y * ps.scaleY;
|
|
12507
13329
|
if (px < minX) minX = px;
|
|
@@ -12513,13 +13335,13 @@ function mountScene(rt, cfg) {
|
|
|
12513
13335
|
if (s > maxS) maxS = s;
|
|
12514
13336
|
}
|
|
12515
13337
|
return {
|
|
12516
|
-
live,
|
|
13338
|
+
live: live2,
|
|
12517
13339
|
max: ps.maxCount,
|
|
12518
13340
|
blend: ps.blend,
|
|
12519
13341
|
renderer: ps.renderers.map((r) => r.kind).join("+"),
|
|
12520
13342
|
origin: [Math.round(ps.originX), Math.round(ps.originY)],
|
|
12521
|
-
bbox:
|
|
12522
|
-
size:
|
|
13343
|
+
bbox: live2 ? [Math.round(minX), Math.round(minY), Math.round(maxX), Math.round(maxY)] : null,
|
|
13344
|
+
size: live2 ? [Math.round(minS), Math.round(maxS)] : null
|
|
12523
13345
|
};
|
|
12524
13346
|
});
|
|
12525
13347
|
window.__particleToggle = (on, onlyIndex) => {
|
|
@@ -12685,8 +13507,11 @@ function mountScene(rt, cfg) {
|
|
|
12685
13507
|
try {
|
|
12686
13508
|
const fe = pkg.getEntry(parsedPkg, fp);
|
|
12687
13509
|
if (!fe) continue;
|
|
13510
|
+
const bytes = sanitizeFontForBrowser(
|
|
13511
|
+
fe instanceof Uint8Array ? fe : new Uint8Array(fe)
|
|
13512
|
+
);
|
|
12688
13513
|
const fam = "wefont_" + fp.split("/").pop().replace(/[^a-zA-Z0-9]/g, "_");
|
|
12689
|
-
const url = URL.createObjectURL(new Blob([
|
|
13514
|
+
const url = URL.createObjectURL(new Blob([bytes]));
|
|
12690
13515
|
const ff = new FontFace(fam, `url(${url})`);
|
|
12691
13516
|
await ff.load();
|
|
12692
13517
|
document.fonts.add(ff);
|
|
@@ -12730,7 +13555,7 @@ function mountScene(rt, cfg) {
|
|
|
12730
13555
|
// 与对象/效果开关/常量/general 统一走 engineTimers(P1-1)。
|
|
12731
13556
|
...timerOpts,
|
|
12732
13557
|
mediaControl,
|
|
12733
|
-
windowTitle:
|
|
13558
|
+
windowTitle: windowDriver.snapshot,
|
|
12734
13559
|
openUserShortcut: shortcuts.openUserShortcut,
|
|
12735
13560
|
getLayerText: (name) => textLayerText.get(name),
|
|
12736
13561
|
onError: (e) => {
|
|
@@ -12978,7 +13803,7 @@ function mountScene(rt, cfg) {
|
|
|
12978
13803
|
return clone;
|
|
12979
13804
|
},
|
|
12980
13805
|
mediaControl,
|
|
12981
|
-
windowTitle:
|
|
13806
|
+
windowTitle: windowDriver.snapshot,
|
|
12982
13807
|
openUserShortcut: shortcuts.openUserShortcut,
|
|
12983
13808
|
isScreensaver: false
|
|
12984
13809
|
};
|
|
@@ -13005,8 +13830,8 @@ function mountScene(rt, cfg) {
|
|
|
13005
13830
|
const ctrl = anim.createAnimation(def.animation);
|
|
13006
13831
|
ctrl.field = field;
|
|
13007
13832
|
ctrl.baseValue = def.value;
|
|
13008
|
-
const
|
|
13009
|
-
ctrl.baseNumeric = Array.isArray(
|
|
13833
|
+
const live2 = layer[field];
|
|
13834
|
+
ctrl.baseNumeric = Array.isArray(live2) ? live2.slice() : live2;
|
|
13010
13835
|
layer.animationList.push(ctrl);
|
|
13011
13836
|
if (ctrl.name) layer.animations[ctrl.name] = ctrl;
|
|
13012
13837
|
animRuns.push({ layer, field, ctrl });
|
|
@@ -13223,8 +14048,10 @@ function mountScene(rt, cfg) {
|
|
|
13223
14048
|
const t = (now - start - pauseAccum) / 1e3;
|
|
13224
14049
|
inputView.update(pointerSrc.state);
|
|
13225
14050
|
if (mediaSim.enabled) {
|
|
13226
|
-
|
|
13227
|
-
|
|
14051
|
+
if (live?.media) live.media.pump();
|
|
14052
|
+
else simMedia.update(t);
|
|
14053
|
+
const snap = mediaSnapshot();
|
|
14054
|
+
const evts = media.diffMediaEvents(lastMediaSnap, snap);
|
|
13228
14055
|
if (evts.length) {
|
|
13229
14056
|
for (const { name, event } of evts) {
|
|
13230
14057
|
for (const sb of mediaHooks) {
|
|
@@ -13232,10 +14059,11 @@ function mountScene(rt, cfg) {
|
|
|
13232
14059
|
sb.callMedia(name, event);
|
|
13233
14060
|
}
|
|
13234
14061
|
}
|
|
13235
|
-
lastMediaSnap = media.cloneMediaSnapshot(
|
|
14062
|
+
lastMediaSnap = media.cloneMediaSnapshot(snap);
|
|
13236
14063
|
}
|
|
13237
14064
|
}
|
|
13238
|
-
|
|
14065
|
+
if (live?.windowTitle) live.windowTitle.pump();
|
|
14066
|
+
else simWindow.update(t);
|
|
13239
14067
|
for (const run of animRuns) {
|
|
13240
14068
|
run.ctrl.advance(interval / 1e3);
|
|
13241
14069
|
const field = run.field;
|
|
@@ -13303,8 +14131,12 @@ function mountScene(rt, cfg) {
|
|
|
13303
14131
|
}
|
|
13304
14132
|
if (visibilityDirty) recomputeVisibility();
|
|
13305
14133
|
if (audioSim.enabled) {
|
|
13306
|
-
|
|
13307
|
-
|
|
14134
|
+
if (audioDriverRef.current) audioDriverRef.current.pump();
|
|
14135
|
+
else simAudio.update(t);
|
|
14136
|
+
fillAudioBuffers(
|
|
14137
|
+
audioViews,
|
|
14138
|
+
audioDriverRef.current ? audioDriverRef.current.snapshot : simAudio.snapshot
|
|
14139
|
+
);
|
|
13308
14140
|
}
|
|
13309
14141
|
if (attachFollows.length) {
|
|
13310
14142
|
mdl.followAttachments(attachFollows, t, getBoneOverrides);
|