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.global.js
CHANGED
|
@@ -999,6 +999,7 @@
|
|
|
999
999
|
// anchor 是盒子相对 origin 的锚点(同 image alignment 枚举,外加 "none")。
|
|
1000
1000
|
// 本机 563 个文字层:none 237 / 缺省 301 / center 20 —— none 与缺省都按 center 处理
|
|
1001
1001
|
// (WE 对象缺省对齐就是 center;显式 center 的挂件与时钟层行为一致)。
|
|
1002
|
+
// 2780710296 实验过默认改 top:竖直阶梯对了,但会平移其它壁纸文字相对图元的位置,已回滚。
|
|
1002
1003
|
textAnchor: typeof o.anchor === "string" && o.anchor !== "none" ? o.anchor : "center",
|
|
1003
1004
|
textMaxwidth: parseNum(o.maxwidth, 0),
|
|
1004
1005
|
textMaxrows: parseNum(o.maxrows, 0),
|
|
@@ -2429,6 +2430,45 @@
|
|
|
2429
2430
|
}
|
|
2430
2431
|
}
|
|
2431
2432
|
}
|
|
2433
|
+
{
|
|
2434
|
+
const inVecN = /* @__PURE__ */ new Map();
|
|
2435
|
+
const declRe = /^\s*in\s+(?:highp|mediump|lowp\s+)?(vec[34])\s+([A-Za-z_]\w*)\s*;/gm;
|
|
2436
|
+
let dm;
|
|
2437
|
+
while ((dm = declRe.exec(code)) !== null) inVecN.set(dm[2], Number(dm[1].slice(3)));
|
|
2438
|
+
const localVecN = new Map(inVecN);
|
|
2439
|
+
const locRe = /\b(vec[34])\s+([A-Za-z_]\w*)\s*[=;]/g;
|
|
2440
|
+
while ((dm = locRe.exec(code)) !== null) {
|
|
2441
|
+
if (!localVecN.has(dm[2])) localVecN.set(dm[2], Number(dm[1].slice(3)));
|
|
2442
|
+
}
|
|
2443
|
+
if (localVecN.size > 0) {
|
|
2444
|
+
const swizzleUvArg = (arg) => {
|
|
2445
|
+
const t = arg.trim();
|
|
2446
|
+
if (!t) return arg;
|
|
2447
|
+
const bare = /^([A-Za-z_]\w*)$/.exec(t);
|
|
2448
|
+
if (bare && localVecN.has(bare[1])) return bare[1] + ".xy";
|
|
2449
|
+
const bin = /^([A-Za-z_]\w*)(\s*[+\-].+)$/.exec(t);
|
|
2450
|
+
if (bin && localVecN.has(bin[1])) return "(" + bin[1] + ".xy" + bin[2] + ")";
|
|
2451
|
+
return arg;
|
|
2452
|
+
};
|
|
2453
|
+
for (const fn of ["textureLod", "texture"]) {
|
|
2454
|
+
code = rewriteCall(code, fn, (inner) => {
|
|
2455
|
+
const args = splitArgs(inner);
|
|
2456
|
+
if (args.length >= 2) args[1] = swizzleUvArg(args[1]);
|
|
2457
|
+
return fn + "(" + args.join(", ") + ")";
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
if (inVecN.size > 0) {
|
|
2462
|
+
code = code.split("\n").map((line) => {
|
|
2463
|
+
if (!/\bvec2\s+[A-Za-z_]\w*\s*=/.test(line)) return line;
|
|
2464
|
+
let out = line;
|
|
2465
|
+
for (const name of inVecN.keys()) {
|
|
2466
|
+
out = out.replace(new RegExp("\\b" + name + "\\b(?!\\s*[.\\w])", "g"), name + ".xy");
|
|
2467
|
+
}
|
|
2468
|
+
return out;
|
|
2469
|
+
}).join("\n");
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2432
2472
|
const written = /* @__PURE__ */ new Set();
|
|
2433
2473
|
const inNames = /* @__PURE__ */ new Set();
|
|
2434
2474
|
{
|
|
@@ -2724,7 +2764,8 @@ void main() {
|
|
|
2724
2764
|
return s;
|
|
2725
2765
|
}
|
|
2726
2766
|
function parseVec3Local(s) {
|
|
2727
|
-
|
|
2767
|
+
if (s !== null && typeof s === "object" && "value" in s) s = s.value;
|
|
2768
|
+
const p = String(s ?? "").trim().split(/\s+/).map(Number);
|
|
2728
2769
|
return [p[0] || 0, p[1] || 0, p[2] || 0];
|
|
2729
2770
|
}
|
|
2730
2771
|
function makeTexture(gl, rgba, width, height, bitmap = null) {
|
|
@@ -3273,7 +3314,11 @@ void main() {
|
|
|
3273
3314
|
}
|
|
3274
3315
|
}
|
|
3275
3316
|
const key = shaderName + "|" + JSON.stringify(effectiveCombos);
|
|
3276
|
-
if (progCache.has(key))
|
|
3317
|
+
if (progCache.has(key)) {
|
|
3318
|
+
const hit = progCache.get(key);
|
|
3319
|
+
if (hit === null) throw new Error("shader=" + shaderName + " 编译失败(已缓存)");
|
|
3320
|
+
return hit;
|
|
3321
|
+
}
|
|
3277
3322
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
3278
3323
|
const missing = /* @__PURE__ */ new Set();
|
|
3279
3324
|
const resolver = (file) => {
|
|
@@ -3288,6 +3333,7 @@ void main() {
|
|
|
3288
3333
|
try {
|
|
3289
3334
|
prog = linkProgram(gl, vertGlsl, fragGlsl);
|
|
3290
3335
|
} catch (e) {
|
|
3336
|
+
progCache.set(key, null);
|
|
3291
3337
|
throw new Error("shader=" + shaderName + " " + (e && e.message));
|
|
3292
3338
|
}
|
|
3293
3339
|
const uni = /* @__PURE__ */ new Map();
|
|
@@ -4481,7 +4527,10 @@ void main() {
|
|
|
4481
4527
|
try {
|
|
4482
4528
|
progEntry = await getEffectProgram(mp.shader, combos, mergedTex);
|
|
4483
4529
|
} catch (e) {
|
|
4484
|
-
|
|
4530
|
+
const msg = e && e.message || String(e);
|
|
4531
|
+
if (!/已缓存/.test(msg)) {
|
|
4532
|
+
console.warn("[we-scene] 跳过效果(pass 编译失败):", mp.shader, msg);
|
|
4533
|
+
}
|
|
4485
4534
|
failedEffects.add(eff2);
|
|
4486
4535
|
continue;
|
|
4487
4536
|
}
|
|
@@ -4831,6 +4880,7 @@ layout(location=1) in vec3 a_pos; // 实例中心(投影空间世界
|
|
|
4831
4880
|
layout(location=2) in vec2 a_sizeRot; // x=size(像素) y=rot(弧度)
|
|
4832
4881
|
layout(location=3) in vec4 a_color; // rgb + alpha
|
|
4833
4882
|
layout(location=4) in vec3 a_stretchFrame; // xy=非等比拉伸 z=帧序号
|
|
4883
|
+
layout(location=5) in vec2 a_vrange; // 段两端沿贴图 v 的取值(rope 连线用;普通精灵 0..1)
|
|
4834
4884
|
uniform mat4 u_mvp;
|
|
4835
4885
|
// 序列帧 uv 变换表(TEXS 帧矩形归一化后的 offset/scale),最多 128 帧
|
|
4836
4886
|
// (matrix spritesheet 72 有 71 帧,旧上限 64 会丢末尾字符)
|
|
@@ -4848,7 +4898,8 @@ void main(){
|
|
|
4848
4898
|
gl_Position = u_mvp * vec4(a_pos.xy + rotated, a_pos.z, 1.0);
|
|
4849
4899
|
// quad 角 → 贴图 uv。世界 y 已翻转到投影空间(y 向下),故 quad 的 +y 角
|
|
4850
4900
|
// 对应屏幕上方,应采样纹理顶行 v=1(与 renderer.js 的 layerQuadVerts 同约定)。
|
|
4851
|
-
|
|
4901
|
+
// a_vrange 让 rope 段两端各取自己的 v(沿绳连续渐变);普通精灵是 (0,1) 恒等。
|
|
4902
|
+
vec2 uv = vec2(a_corner.x + 0.5, mix(a_vrange.x, a_vrange.y, a_corner.y + 0.5));
|
|
4852
4903
|
if (u_frameCount > 0) {
|
|
4853
4904
|
// 帧矩形以左上为原点(TEXS 是 top-down 像素坐标),故先把 v 翻成 top-down
|
|
4854
4905
|
int fi = int(a_stretchFrame.z);
|
|
@@ -4922,7 +4973,7 @@ void main(){
|
|
|
4922
4973
|
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 8, 0);
|
|
4923
4974
|
gl.vertexAttribDivisor(0, 0);
|
|
4924
4975
|
gl.bindBuffer(gl.ARRAY_BUFFER, vbuf);
|
|
4925
|
-
const S =
|
|
4976
|
+
const S = 56;
|
|
4926
4977
|
gl.enableVertexAttribArray(1);
|
|
4927
4978
|
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, S, 0);
|
|
4928
4979
|
gl.vertexAttribDivisor(1, 1);
|
|
@@ -4935,6 +4986,9 @@ void main(){
|
|
|
4935
4986
|
gl.enableVertexAttribArray(4);
|
|
4936
4987
|
gl.vertexAttribPointer(4, 3, gl.FLOAT, false, S, 36);
|
|
4937
4988
|
gl.vertexAttribDivisor(4, 1);
|
|
4989
|
+
gl.enableVertexAttribArray(5);
|
|
4990
|
+
gl.vertexAttribPointer(5, 2, gl.FLOAT, false, S, 48);
|
|
4991
|
+
gl.vertexAttribDivisor(5, 1);
|
|
4938
4992
|
gl.bindVertexArray(null);
|
|
4939
4993
|
return {
|
|
4940
4994
|
prog: {
|
|
@@ -4986,6 +5040,20 @@ void main(){
|
|
|
4986
5040
|
function particleInstanceSegs(trailCfg, trailSegments) {
|
|
4987
5041
|
return trailCfg && trailCfg.kind === "ropetrail" ? Math.max(1, trailSegments || 1) : 1;
|
|
4988
5042
|
}
|
|
5043
|
+
function ropeTrailHistoryCount(cfg) {
|
|
5044
|
+
if (!cfg || cfg.kind !== "ropetrail") return 1;
|
|
5045
|
+
const segs = Math.round(Number(cfg.segments) || 0);
|
|
5046
|
+
if (segs >= 2) return Math.min(32, segs);
|
|
5047
|
+
return 8;
|
|
5048
|
+
}
|
|
5049
|
+
function ropeTrailDuration(cfg) {
|
|
5050
|
+
if (!cfg || cfg.kind !== "ropetrail") return 0;
|
|
5051
|
+
const L = Number(cfg.length);
|
|
5052
|
+
return Number.isFinite(L) && L > 0 ? L : 0.2;
|
|
5053
|
+
}
|
|
5054
|
+
function ropeParticleV(p) {
|
|
5055
|
+
return p.life > 0 ? p.age / p.life : 0;
|
|
5056
|
+
}
|
|
4989
5057
|
class Particle {
|
|
4990
5058
|
constructor() {
|
|
4991
5059
|
this.alive = false;
|
|
@@ -5014,6 +5082,8 @@ void main(){
|
|
|
5014
5082
|
this.turbSpeed = 0;
|
|
5015
5083
|
this.turbPhase = 0;
|
|
5016
5084
|
this.trail = null;
|
|
5085
|
+
this.trailClock = 0;
|
|
5086
|
+
this.seq = 0;
|
|
5017
5087
|
}
|
|
5018
5088
|
}
|
|
5019
5089
|
class ParticleSystem {
|
|
@@ -5074,6 +5144,9 @@ void main(){
|
|
|
5074
5144
|
this._followParent = null;
|
|
5075
5145
|
this._followMode = null;
|
|
5076
5146
|
this._followOffset = [0, 0, 0];
|
|
5147
|
+
this.ropeRenderer = null;
|
|
5148
|
+
this._seq = 0;
|
|
5149
|
+
this._ropeOrder = [];
|
|
5077
5150
|
this._ov = {};
|
|
5078
5151
|
this._applyOverride();
|
|
5079
5152
|
this.startTime = Math.max(0, Math.min(30, num(this.model.starttime, 0)));
|
|
@@ -5350,24 +5423,34 @@ void main(){
|
|
|
5350
5423
|
const kind = r && r.name || "sprite";
|
|
5351
5424
|
return {
|
|
5352
5425
|
kind,
|
|
5353
|
-
length: num(r && r.length, kind === "spritetrail" ? 0.1 : 0),
|
|
5426
|
+
length: num(r && r.length, kind === "spritetrail" ? 0.1 : kind === "ropetrail" ? 0.2 : 0),
|
|
5354
5427
|
maxLength: num(r && r.maxlength, 0),
|
|
5355
5428
|
minLength: num(r && r.minlength, 0),
|
|
5356
5429
|
subdivision: num(r && r.subdivision, 1),
|
|
5430
|
+
// Rope Trail 段数(官方 `segments`);与 spritetrail 的 maxlength 无关
|
|
5431
|
+
segments: num(r && r.segments, 0),
|
|
5357
5432
|
orientation: r && r.orientation || null
|
|
5358
5433
|
};
|
|
5359
5434
|
});
|
|
5360
5435
|
if (omitted && !this.renderers.length) {
|
|
5361
|
-
this.renderers = [{ kind: "sprite", length: 0, maxLength: 0, minLength: 0, subdivision: 1, orientation: null }];
|
|
5436
|
+
this.renderers = [{ kind: "sprite", length: 0, maxLength: 0, minLength: 0, subdivision: 1, segments: 0, orientation: null }];
|
|
5362
5437
|
}
|
|
5363
5438
|
const tr = this.renderers.find((r) => r.kind === "spritetrail" || r.kind === "ropetrail");
|
|
5364
5439
|
this.trailCfg = tr || null;
|
|
5365
5440
|
this.trailSegments = 1;
|
|
5441
|
+
this.trailDuration = 0;
|
|
5442
|
+
this.trailSampleDt = 0;
|
|
5366
5443
|
if (tr && tr.kind === "ropetrail") {
|
|
5367
|
-
const segs =
|
|
5444
|
+
const segs = ropeTrailHistoryCount(tr);
|
|
5368
5445
|
this.trailSegments = segs;
|
|
5369
|
-
|
|
5446
|
+
this.trailDuration = ropeTrailDuration(tr);
|
|
5447
|
+
this.trailSampleDt = this.trailDuration / Math.max(1, segs - 1);
|
|
5448
|
+
for (const p of this.pool) {
|
|
5449
|
+
p.trail = new Float32Array(segs * 3);
|
|
5450
|
+
p.trailClock = 0;
|
|
5451
|
+
}
|
|
5370
5452
|
}
|
|
5453
|
+
this.ropeRenderer = this.renderers.find((r) => r.kind === "rope") || null;
|
|
5371
5454
|
}
|
|
5372
5455
|
setModel(model) {
|
|
5373
5456
|
this.model = model;
|
|
@@ -5498,6 +5581,7 @@ void main(){
|
|
|
5498
5581
|
p.rotVel = 0;
|
|
5499
5582
|
p.vx = p.vy = p.vz = 0;
|
|
5500
5583
|
p.frame = 0;
|
|
5584
|
+
p.seq = this._seq++;
|
|
5501
5585
|
const o = em.origin;
|
|
5502
5586
|
if (em.kind === "box") {
|
|
5503
5587
|
const d = em.distanceMax || [0, 0, 0];
|
|
@@ -5644,6 +5728,7 @@ void main(){
|
|
|
5644
5728
|
p.trail[i + 1] = p.y;
|
|
5645
5729
|
p.trail[i + 2] = p.z;
|
|
5646
5730
|
}
|
|
5731
|
+
p.trailClock = 0;
|
|
5647
5732
|
}
|
|
5648
5733
|
}
|
|
5649
5734
|
// ---------- 每粒子更新 ----------
|
|
@@ -5794,10 +5879,20 @@ void main(){
|
|
|
5794
5879
|
}
|
|
5795
5880
|
if (p.trail) {
|
|
5796
5881
|
const tr = p.trail;
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5882
|
+
const step = this.trailSampleDt;
|
|
5883
|
+
if (step > 0) {
|
|
5884
|
+
p.trailClock = (p.trailClock || 0) + dt;
|
|
5885
|
+
let shifts = 0;
|
|
5886
|
+
const cap = this.trailSegments || 8;
|
|
5887
|
+
while (p.trailClock >= step && shifts < cap) {
|
|
5888
|
+
p.trailClock -= step;
|
|
5889
|
+
shifts++;
|
|
5890
|
+
for (let i = tr.length - 3; i >= 3; i -= 3) {
|
|
5891
|
+
tr[i] = tr[i - 3];
|
|
5892
|
+
tr[i + 1] = tr[i - 2];
|
|
5893
|
+
tr[i + 2] = tr[i - 1];
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5801
5896
|
}
|
|
5802
5897
|
tr[0] = p.x;
|
|
5803
5898
|
tr[1] = p.y;
|
|
@@ -5892,13 +5987,22 @@ void main(){
|
|
|
5892
5987
|
if (!this._prog) this._buildProgram(gl);
|
|
5893
5988
|
const trail = this.trailCfg && this.trailCfg.kind === "ropetrail" ? this.trailCfg : null;
|
|
5894
5989
|
const spriteTrail = this.trailCfg && this.trailCfg.kind === "spritetrail" ? this.trailCfg : null;
|
|
5990
|
+
const rope = this.ropeRenderer;
|
|
5895
5991
|
const segs = particleInstanceSegs(this.trailCfg, this.trailSegments);
|
|
5896
|
-
const STRIDE =
|
|
5992
|
+
const STRIDE = 14;
|
|
5897
5993
|
const pool = this.pool;
|
|
5994
|
+
let order = null;
|
|
5995
|
+
if (rope) {
|
|
5996
|
+
order = this._ropeOrder;
|
|
5997
|
+
order.length = 0;
|
|
5998
|
+
for (let i = 0; i < pool.length; i++) if (pool[i].alive) order.push(pool[i]);
|
|
5999
|
+
order.sort((a, b) => a.seq - b.seq);
|
|
6000
|
+
}
|
|
5898
6001
|
let live = 0;
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
6002
|
+
if (order) live = order.length;
|
|
6003
|
+
else for (let i = 0; i < pool.length; i++) if (pool[i].alive) live++;
|
|
6004
|
+
if (live === 0 || rope && live < 2) return;
|
|
6005
|
+
const instCount = rope ? live - 1 : live * segs;
|
|
5902
6006
|
const need = instCount * STRIDE;
|
|
5903
6007
|
if (!this._data || this._data.length < need) this._data = new Float32Array(Math.max(need, 1024));
|
|
5904
6008
|
const data = this._data;
|
|
@@ -5918,7 +6022,34 @@ void main(){
|
|
|
5918
6022
|
const py = ly * sy;
|
|
5919
6023
|
return [ox + px * cos - py * sin, projH - (oy + px * sin + py * cos)];
|
|
5920
6024
|
};
|
|
5921
|
-
|
|
6025
|
+
if (rope) {
|
|
6026
|
+
for (let i = 0; i + 1 < live; i++) {
|
|
6027
|
+
const a = order[i];
|
|
6028
|
+
const b = order[i + 1];
|
|
6029
|
+
const wa = toWorld(a.x, a.y);
|
|
6030
|
+
const wb = toWorld(b.x, b.y);
|
|
6031
|
+
const dx = wb[0] - wa[0];
|
|
6032
|
+
const dy = wb[1] - wa[1];
|
|
6033
|
+
const dist = Math.hypot(dx, dy);
|
|
6034
|
+
const width2 = (Math.abs(a.size) + Math.abs(b.size)) * 0.5 * sysScale;
|
|
6035
|
+
if (!(width2 > 0)) continue;
|
|
6036
|
+
data[k++] = (wa[0] + wb[0]) * 0.5;
|
|
6037
|
+
data[k++] = (wa[1] + wb[1]) * 0.5;
|
|
6038
|
+
data[k++] = 0;
|
|
6039
|
+
data[k++] = width2;
|
|
6040
|
+
data[k++] = Math.atan2(-dx, dy);
|
|
6041
|
+
data[k++] = (a.r + b.r) * 0.5 * bright;
|
|
6042
|
+
data[k++] = (a.g + b.g) * 0.5 * bright;
|
|
6043
|
+
data[k++] = (a.b + b.b) * 0.5 * bright;
|
|
6044
|
+
data[k++] = (a.alpha + b.alpha) * 0.5;
|
|
6045
|
+
data[k++] = 1;
|
|
6046
|
+
data[k++] = dist / width2;
|
|
6047
|
+
data[k++] = 0;
|
|
6048
|
+
data[k++] = ropeParticleV(a);
|
|
6049
|
+
data[k++] = ropeParticleV(b);
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
for (let i = 0; i < pool.length && !rope; i++) {
|
|
5922
6053
|
const p = pool[i];
|
|
5923
6054
|
if (!p.alive) continue;
|
|
5924
6055
|
for (let s = 0; s < segs; s++) {
|
|
@@ -5926,18 +6057,53 @@ void main(){
|
|
|
5926
6057
|
let ly = p.y;
|
|
5927
6058
|
let segAlpha = 1;
|
|
5928
6059
|
let segSize = 1;
|
|
6060
|
+
let rot = p.rot;
|
|
6061
|
+
let instStretchX = stretchX;
|
|
6062
|
+
let instStretchY = stretchY;
|
|
6063
|
+
let wx;
|
|
6064
|
+
let wy;
|
|
5929
6065
|
if (trail && p.trail) {
|
|
5930
6066
|
lx = p.trail[s * 3];
|
|
5931
6067
|
ly = p.trail[s * 3 + 1];
|
|
5932
6068
|
const t = segs > 1 ? s / (segs - 1) : 0;
|
|
5933
6069
|
segAlpha = 1 - t;
|
|
5934
6070
|
segSize = 1 - t * 0.55;
|
|
6071
|
+
let tdx = 0;
|
|
6072
|
+
let tdy = 0;
|
|
6073
|
+
if (s + 1 < segs) {
|
|
6074
|
+
tdx = p.trail[s * 3] - p.trail[(s + 1) * 3];
|
|
6075
|
+
tdy = p.trail[s * 3 + 1] - p.trail[(s + 1) * 3 + 1];
|
|
6076
|
+
} else if (s > 0) {
|
|
6077
|
+
tdx = p.trail[(s - 1) * 3] - p.trail[s * 3];
|
|
6078
|
+
tdy = p.trail[(s - 1) * 3 + 1] - p.trail[s * 3 + 1];
|
|
6079
|
+
} else {
|
|
6080
|
+
tdx = p.vx;
|
|
6081
|
+
tdy = p.vy;
|
|
6082
|
+
}
|
|
6083
|
+
const w0 = toWorld(lx, ly);
|
|
6084
|
+
const w1 = toWorld(lx - tdx, ly - tdy);
|
|
6085
|
+
const dx = w0[0] - w1[0];
|
|
6086
|
+
const dy = w0[1] - w1[1];
|
|
6087
|
+
const dist = Math.hypot(dx, dy);
|
|
6088
|
+
const base = Math.max(1e-3, Math.abs(p.size) * sysScale * segSize);
|
|
6089
|
+
if (dist > 1e-3) {
|
|
6090
|
+
rot = spriteTrailRotation(dx, dy);
|
|
6091
|
+
wx = (w0[0] + w1[0]) * 0.5;
|
|
6092
|
+
wy = (w0[1] + w1[1]) * 0.5;
|
|
6093
|
+
instStretchY = Math.max(stretchY, dist / base);
|
|
6094
|
+
} else {
|
|
6095
|
+
wx = w0[0];
|
|
6096
|
+
wy = w0[1];
|
|
6097
|
+
}
|
|
6098
|
+
} else {
|
|
6099
|
+
const w = toWorld(lx, ly);
|
|
6100
|
+
wx = w[0];
|
|
6101
|
+
wy = w[1];
|
|
5935
6102
|
}
|
|
5936
|
-
const w = toWorld(lx, ly);
|
|
5937
|
-
let rot = p.rot;
|
|
5938
|
-
let instStretchX = stretchX;
|
|
5939
|
-
let instStretchY = stretchY;
|
|
5940
6103
|
if (spriteTrail) {
|
|
6104
|
+
const w = toWorld(p.x, p.y);
|
|
6105
|
+
wx = w[0];
|
|
6106
|
+
wy = w[1];
|
|
5941
6107
|
const w1 = toWorld(p.x + p.vx, p.y + p.vy);
|
|
5942
6108
|
rot = spriteTrailRotation(w1[0] - w[0], w1[1] - w[1]);
|
|
5943
6109
|
const factor = spriteTrailLengthFactor(
|
|
@@ -5948,8 +6114,8 @@ void main(){
|
|
|
5948
6114
|
);
|
|
5949
6115
|
instStretchY = stretchY * factor;
|
|
5950
6116
|
}
|
|
5951
|
-
data[k++] =
|
|
5952
|
-
data[k++] =
|
|
6117
|
+
data[k++] = wx;
|
|
6118
|
+
data[k++] = wy;
|
|
5953
6119
|
data[k++] = 0;
|
|
5954
6120
|
data[k++] = Math.abs(p.size) * sysScale * segSize;
|
|
5955
6121
|
data[k++] = rot;
|
|
@@ -5960,6 +6126,8 @@ void main(){
|
|
|
5960
6126
|
data[k++] = instStretchX;
|
|
5961
6127
|
data[k++] = instStretchY;
|
|
5962
6128
|
data[k++] = p.frame;
|
|
6129
|
+
data[k++] = 0;
|
|
6130
|
+
data[k++] = 1;
|
|
5963
6131
|
}
|
|
5964
6132
|
}
|
|
5965
6133
|
const prog = this._prog;
|
|
@@ -6093,6 +6261,9 @@ void main(){
|
|
|
6093
6261
|
particleInstanceSegs,
|
|
6094
6262
|
particlePassRefract,
|
|
6095
6263
|
rgbaIsBlankWhite,
|
|
6264
|
+
ropeParticleV,
|
|
6265
|
+
ropeTrailDuration,
|
|
6266
|
+
ropeTrailHistoryCount,
|
|
6096
6267
|
spriteTrailLengthFactor,
|
|
6097
6268
|
spriteTrailRotation
|
|
6098
6269
|
}, Symbol.toStringTag, { value: "Module" }));
|
|
@@ -6198,7 +6369,7 @@ void main(){
|
|
|
6198
6369
|
return { width: size, height: size, rgba };
|
|
6199
6370
|
}
|
|
6200
6371
|
function beam(w, h, coreWidth, fadeBoth, peak) {
|
|
6201
|
-
const pk =
|
|
6372
|
+
const pk = 0.55;
|
|
6202
6373
|
const rgba = new Uint8Array(w * h * 4);
|
|
6203
6374
|
for (let y = 0; y < h; y++) {
|
|
6204
6375
|
const ty = y / (h - 1);
|
|
@@ -6211,6 +6382,41 @@ void main(){
|
|
|
6211
6382
|
}
|
|
6212
6383
|
return { width: w, height: h, rgba };
|
|
6213
6384
|
}
|
|
6385
|
+
const RAIN_FRAMES = 4;
|
|
6386
|
+
function rainStreak(w, h, tiltDeg, corePx, peak) {
|
|
6387
|
+
const pk = peak === void 0 ? 0.85 : peak;
|
|
6388
|
+
const rgba = new Uint8Array(w * h * 4);
|
|
6389
|
+
const fh = h / RAIN_FRAMES;
|
|
6390
|
+
const rng = mulberry32$1(335009);
|
|
6391
|
+
const tilt = Math.tan(tiltDeg * Math.PI / 180);
|
|
6392
|
+
const cx = w / 2;
|
|
6393
|
+
for (let f = 0; f < RAIN_FRAMES; f++) {
|
|
6394
|
+
const peakF = pk * (0.8 + rng() * 0.35);
|
|
6395
|
+
const coreF = corePx * (0.85 + rng() * 0.5);
|
|
6396
|
+
const ph = (rng() - 0.5) * w * 0.12;
|
|
6397
|
+
for (let y = 0; y < fh; y++) {
|
|
6398
|
+
const ty = y / (fh - 1);
|
|
6399
|
+
const lineX = cx + ph + (fh - 1) * tilt / 2 - (fh - 1) * tilt * ty;
|
|
6400
|
+
const vy = gauss(ty - 0.5, 0.27);
|
|
6401
|
+
for (let x = 0; x < w; x++) {
|
|
6402
|
+
const d = Math.abs(x + 0.5 - lineX);
|
|
6403
|
+
const g = Math.exp(-(d * d) / (coreF * coreF));
|
|
6404
|
+
const a = g * vy * peakF;
|
|
6405
|
+
if (a < 4e-3) continue;
|
|
6406
|
+
writeWhite(rgba, ((f * fh + y) * w + x) * 4, a);
|
|
6407
|
+
}
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
6410
|
+
return { width: w, height: h, rgba };
|
|
6411
|
+
}
|
|
6412
|
+
function builtinParticleFrames(name) {
|
|
6413
|
+
if (name === "particle/nature/rain1" || name === "particle/nature/rain2") {
|
|
6414
|
+
const list = [];
|
|
6415
|
+
for (let i = 0; i < RAIN_FRAMES; i++) list.push({ ou: 0, ov: i / RAIN_FRAMES, su: 1, sv: 1 / RAIN_FRAMES });
|
|
6416
|
+
return list;
|
|
6417
|
+
}
|
|
6418
|
+
return null;
|
|
6419
|
+
}
|
|
6214
6420
|
function ring(size, radius, thickness) {
|
|
6215
6421
|
const rgba = new Uint8Array(size * size * 4);
|
|
6216
6422
|
const half = size / 2;
|
|
@@ -6891,8 +7097,8 @@ void main(){
|
|
|
6891
7097
|
// 水滴(原生 64×256 → 128×512):竖长泪滴
|
|
6892
7098
|
"particle/drop": () => teardrop(128, 512),
|
|
6893
7099
|
// 雨丝(原生 64×256 → 128×512):细长条,两端渐隐
|
|
6894
|
-
"particle/nature/rain1": () =>
|
|
6895
|
-
"particle/nature/rain2": () =>
|
|
7100
|
+
"particle/nature/rain1": () => rainStreak(128, 512, 10, 1, 0.78),
|
|
7101
|
+
"particle/nature/rain2": () => rainStreak(128, 512, 10, 1.6, 0.66),
|
|
6896
7102
|
// 雨滴 sheet(原生 128×256 → 256×512,2×4 格):每格一颗上圆下尖小水滴
|
|
6897
7103
|
"particle/water/rain_drops_sheet": () => dropSheet(256, 512, 2, 4),
|
|
6898
7104
|
// 雾(原生 256 → 512):絮状 fBm,弱遮罩铺满;三张不同尺度/种子
|
|
@@ -7010,6 +7216,7 @@ void main(){
|
|
|
7010
7216
|
const particleTexMod = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
7011
7217
|
__proto__: null,
|
|
7012
7218
|
buildBuiltinParticleTexture,
|
|
7219
|
+
builtinParticleFrames,
|
|
7013
7220
|
isBuiltinParticleTextureName,
|
|
7014
7221
|
listBuiltinParticleTextureNames,
|
|
7015
7222
|
particleNormalNameForAlbedo,
|
|
@@ -7627,6 +7834,10 @@ void main(){
|
|
|
7627
7834
|
c.origin[0] += d[0];
|
|
7628
7835
|
c.origin[1] += d[1];
|
|
7629
7836
|
}
|
|
7837
|
+
layer.parallaxDepth = parent.parallaxDepth ? parent.parallaxDepth.slice() : null;
|
|
7838
|
+
for (const c of desc) {
|
|
7839
|
+
c.parallaxDepth = layer.parallaxDepth ? layer.parallaxDepth.slice() : null;
|
|
7840
|
+
}
|
|
7630
7841
|
follows.push({
|
|
7631
7842
|
layer,
|
|
7632
7843
|
parent,
|
|
@@ -7934,10 +8145,12 @@ void main() {
|
|
|
7934
8145
|
const totalH = lines.length * lineHeight;
|
|
7935
8146
|
const halign = opts.halign || "center";
|
|
7936
8147
|
const valign = opts.valign || "center";
|
|
7937
|
-
const
|
|
8148
|
+
const midX = boxW / 2;
|
|
8149
|
+
const midY = boxH / 2;
|
|
8150
|
+
const y0 = valign === "top" ? midY : valign === "bottom" ? midY - totalH : (boxH - totalH) / 2;
|
|
7938
8151
|
const out = lines.map((text, i) => {
|
|
7939
8152
|
const w = widths[i];
|
|
7940
|
-
const x = halign === "left" ?
|
|
8153
|
+
const x = halign === "left" ? midX : halign === "right" ? midX - w : (boxW - w) / 2;
|
|
7941
8154
|
return { text, width: w, x, y: y0 + i * lineHeight };
|
|
7942
8155
|
});
|
|
7943
8156
|
return { lines: out, lineHeight, totalH, truncated, boxW, boxH };
|
|
@@ -8312,6 +8525,11 @@ void main() {
|
|
|
8312
8525
|
if (v !== null && typeof v === "object" && "value" in v) return v.value;
|
|
8313
8526
|
return v;
|
|
8314
8527
|
}
|
|
8528
|
+
function engineCanvasSize(cs) {
|
|
8529
|
+
const src = cs || { width: 1920, height: 1080 };
|
|
8530
|
+
if (src.x !== void 0 && src.y !== void 0) return src;
|
|
8531
|
+
return { x: src.width, y: src.height, width: src.width, height: src.height };
|
|
8532
|
+
}
|
|
8315
8533
|
function evalTextScript(script, scriptprops, opts = {}) {
|
|
8316
8534
|
if (typeof script !== "string" || script.length === 0) return null;
|
|
8317
8535
|
const body = scriptToFunctionBody(script);
|
|
@@ -8353,7 +8571,7 @@ void main() {
|
|
|
8353
8571
|
},
|
|
8354
8572
|
frametime: 1 / 60,
|
|
8355
8573
|
runtime: 0,
|
|
8356
|
-
canvasSize: opts.canvasSize
|
|
8574
|
+
canvasSize: engineCanvasSize(opts.canvasSize),
|
|
8357
8575
|
// [we-scene patch] engine.screenResolution(全库 10 处 / 2 壁纸):
|
|
8358
8576
|
// 屏幕**像素**尺寸,脚本用它把 input.cursorScreenPosition 归一化
|
|
8359
8577
|
// (3791967416 除它得 [0,1];3509243656 减半屏得 [-1,1])。
|
|
@@ -9303,7 +9521,7 @@ void main() {
|
|
|
9303
9521
|
},
|
|
9304
9522
|
frametime: 1 / 60,
|
|
9305
9523
|
runtime: 0,
|
|
9306
|
-
canvasSize: opts.canvasSize
|
|
9524
|
+
canvasSize: engineCanvasSize(opts.canvasSize),
|
|
9307
9525
|
screenResolution: opts.screenResolution || { x: 1920, y: 1080 },
|
|
9308
9526
|
timeOfDay: typeof opts.timeOfDay === "number" ? opts.timeOfDay : 0,
|
|
9309
9527
|
userProperties: opts.userProperties || {},
|
|
@@ -9738,7 +9956,7 @@ void main() {
|
|
|
9738
9956
|
cancel.handle = state.handle;
|
|
9739
9957
|
return cancel;
|
|
9740
9958
|
}
|
|
9741
|
-
function
|
|
9959
|
+
function setInterval2(fn, ms) {
|
|
9742
9960
|
if (typeof fn !== "function") return makeCancel({ fired: true, handle: null }, null);
|
|
9743
9961
|
const state = { fired: false, handle: null };
|
|
9744
9962
|
const cancel = makeCancel(state, clearI);
|
|
@@ -9754,7 +9972,7 @@ void main() {
|
|
|
9754
9972
|
}
|
|
9755
9973
|
if (clearT && h != null) clearT(h);
|
|
9756
9974
|
}
|
|
9757
|
-
function
|
|
9975
|
+
function clearInterval2(h) {
|
|
9758
9976
|
if (typeof h === "function") {
|
|
9759
9977
|
h();
|
|
9760
9978
|
return;
|
|
@@ -9767,9 +9985,9 @@ void main() {
|
|
|
9767
9985
|
}
|
|
9768
9986
|
return {
|
|
9769
9987
|
setTimeout,
|
|
9770
|
-
setInterval,
|
|
9988
|
+
setInterval: setInterval2,
|
|
9771
9989
|
clearTimeout,
|
|
9772
|
-
clearInterval,
|
|
9990
|
+
clearInterval: clearInterval2,
|
|
9773
9991
|
dispose,
|
|
9774
9992
|
/** 测试与诊断用:尚未触发且未取消的定时器数 */
|
|
9775
9993
|
pendingCount: () => pending.size
|
|
@@ -9819,11 +10037,11 @@ void main() {
|
|
|
9819
10037
|
const patterns = makePatterns(rand2);
|
|
9820
10038
|
const phases = new Float32Array(64);
|
|
9821
10039
|
for (let i = 0; i < 64; i++) phases[i] = rand2() * 64;
|
|
9822
|
-
const
|
|
9823
|
-
const rawL = new Float32Array(
|
|
9824
|
-
const rawR = new Float32Array(
|
|
9825
|
-
const left64 = new Float32Array(
|
|
9826
|
-
const right64 = new Float32Array(
|
|
10040
|
+
const BANDS2 = 64;
|
|
10041
|
+
const rawL = new Float32Array(BANDS2);
|
|
10042
|
+
const rawR = new Float32Array(BANDS2);
|
|
10043
|
+
const left64 = new Float32Array(BANDS2);
|
|
10044
|
+
const right64 = new Float32Array(BANDS2);
|
|
9827
10045
|
const left32 = new Float32Array(32);
|
|
9828
10046
|
const right32 = new Float32Array(32);
|
|
9829
10047
|
const left16 = new Float32Array(16);
|
|
@@ -9840,7 +10058,7 @@ void main() {
|
|
|
9840
10058
|
/** 渲染器诊断:当前是否处于「静音段」 */
|
|
9841
10059
|
silent: false
|
|
9842
10060
|
};
|
|
9843
|
-
function
|
|
10061
|
+
function downsample2(dst, src) {
|
|
9844
10062
|
const g = src.length / dst.length;
|
|
9845
10063
|
for (let i = 0; i < dst.length; i++) {
|
|
9846
10064
|
let s = 0;
|
|
@@ -9867,8 +10085,8 @@ void main() {
|
|
|
9867
10085
|
const hatV = patterns.hat[i16] * hitEnv * drumGate;
|
|
9868
10086
|
const riser = buildup > 0 ? Math.pow(buildup, 3) * (0.4 + 0.6 * Math.abs(vnoise(step * 2, 7))) : 0;
|
|
9869
10087
|
let levelSum = 0;
|
|
9870
|
-
for (let i = 0; i <
|
|
9871
|
-
const fq = i /
|
|
10088
|
+
for (let i = 0; i < BANDS2; i++) {
|
|
10089
|
+
const fq = i / BANDS2;
|
|
9872
10090
|
const tilt = Math.pow(1 - fq * 0.85, 1.6);
|
|
9873
10091
|
let v = midGate * (0.5 + 0.3 * vnoise(beat * 0.5 + phases[i] * 0.05, i % 8));
|
|
9874
10092
|
v *= 0.35 + 0.65 * fq;
|
|
@@ -9892,10 +10110,10 @@ void main() {
|
|
|
9892
10110
|
}
|
|
9893
10111
|
left64.set(rawL);
|
|
9894
10112
|
right64.set(rawR);
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
|
|
10113
|
+
downsample2(left32, rawL);
|
|
10114
|
+
downsample2(right32, rawR);
|
|
10115
|
+
downsample2(left16, rawL);
|
|
10116
|
+
downsample2(right16, rawR);
|
|
9899
10117
|
snapshot.level = Math.min(1, levelSum / (48 * 1.2));
|
|
9900
10118
|
snapshot.silent = silent;
|
|
9901
10119
|
return snapshot;
|
|
@@ -9904,7 +10122,7 @@ void main() {
|
|
|
9904
10122
|
update,
|
|
9905
10123
|
snapshot,
|
|
9906
10124
|
/** 频段基数 */
|
|
9907
|
-
bands:
|
|
10125
|
+
bands: BANDS2
|
|
9908
10126
|
};
|
|
9909
10127
|
}
|
|
9910
10128
|
function fillAudioBuffers(views, snapshot) {
|
|
@@ -9926,7 +10144,7 @@ void main() {
|
|
|
9926
10144
|
dst[i] = s / (i1 - i0);
|
|
9927
10145
|
}
|
|
9928
10146
|
}
|
|
9929
|
-
const MEDIA_PLAYBACK = { STOPPED: 0, PLAYING: 1, PAUSED: 2 };
|
|
10147
|
+
const MEDIA_PLAYBACK$1 = { STOPPED: 0, PLAYING: 1, PAUSED: 2 };
|
|
9930
10148
|
class MediaVec3 {
|
|
9931
10149
|
constructor(x, y, z) {
|
|
9932
10150
|
this.x = Number(x) || 0;
|
|
@@ -10051,7 +10269,7 @@ void main() {
|
|
|
10051
10269
|
const cycle = tracks.reduce((s, t) => s + t.duration + GAP, 0);
|
|
10052
10270
|
const snapshot = {
|
|
10053
10271
|
hasMedia: false,
|
|
10054
|
-
state: MEDIA_PLAYBACK.STOPPED,
|
|
10272
|
+
state: MEDIA_PLAYBACK$1.STOPPED,
|
|
10055
10273
|
title: "",
|
|
10056
10274
|
artist: "",
|
|
10057
10275
|
album: "",
|
|
@@ -10118,10 +10336,10 @@ void main() {
|
|
|
10118
10336
|
snapshot.duration = tr.duration;
|
|
10119
10337
|
snapshot.position = pos;
|
|
10120
10338
|
const frac = tr.duration > 0 ? pos / tr.duration : 0;
|
|
10121
|
-
if (held) snapshot.state = MEDIA_PLAYBACK.PAUSED;
|
|
10122
|
-
else if (inGap) snapshot.state = MEDIA_PLAYBACK.STOPPED;
|
|
10123
|
-
else if (frac > 0.7 && frac < 0.76) snapshot.state = MEDIA_PLAYBACK.PAUSED;
|
|
10124
|
-
else snapshot.state = MEDIA_PLAYBACK.PLAYING;
|
|
10339
|
+
if (held) snapshot.state = MEDIA_PLAYBACK$1.PAUSED;
|
|
10340
|
+
else if (inGap) snapshot.state = MEDIA_PLAYBACK$1.STOPPED;
|
|
10341
|
+
else if (frac > 0.7 && frac < 0.76) snapshot.state = MEDIA_PLAYBACK$1.PAUSED;
|
|
10342
|
+
else snapshot.state = MEDIA_PLAYBACK$1.PLAYING;
|
|
10125
10343
|
snapshot.hasThumbnail = !inGap;
|
|
10126
10344
|
const c = tr.colors;
|
|
10127
10345
|
snapshot.primaryColor = new MediaVec3(c.primary[0], c.primary[1], c.primary[2]);
|
|
@@ -10160,7 +10378,7 @@ void main() {
|
|
|
10160
10378
|
if (held) return snapshot;
|
|
10161
10379
|
holdT = lastWall + seekOffset;
|
|
10162
10380
|
held = true;
|
|
10163
|
-
snapshot.state = MEDIA_PLAYBACK.PAUSED;
|
|
10381
|
+
snapshot.state = MEDIA_PLAYBACK$1.PAUSED;
|
|
10164
10382
|
return snapshot;
|
|
10165
10383
|
}
|
|
10166
10384
|
function play() {
|
|
@@ -10177,7 +10395,7 @@ void main() {
|
|
|
10177
10395
|
snapshot,
|
|
10178
10396
|
tracks,
|
|
10179
10397
|
cycle,
|
|
10180
|
-
MEDIA_PLAYBACK,
|
|
10398
|
+
MEDIA_PLAYBACK: MEDIA_PLAYBACK$1,
|
|
10181
10399
|
skipNext,
|
|
10182
10400
|
skipPrevious,
|
|
10183
10401
|
play,
|
|
@@ -10285,7 +10503,7 @@ void main() {
|
|
|
10285
10503
|
}
|
|
10286
10504
|
const mediaMod = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
10287
10505
|
__proto__: null,
|
|
10288
|
-
MEDIA_PLAYBACK,
|
|
10506
|
+
MEDIA_PLAYBACK: MEDIA_PLAYBACK$1,
|
|
10289
10507
|
cloneMediaSnapshot,
|
|
10290
10508
|
createSimulatedMedia,
|
|
10291
10509
|
diffMediaEvents,
|
|
@@ -11149,6 +11367,382 @@ void main() {
|
|
|
11149
11367
|
const SKIP_PARTICLES = false;
|
|
11150
11368
|
const SKIP_SCENE_EFFECTS = false;
|
|
11151
11369
|
const TEXT_EM_SCALE = 4;
|
|
11370
|
+
const BANDS = 64;
|
|
11371
|
+
const MEDIA_PLAYBACK = { STOPPED: 0 };
|
|
11372
|
+
function zeroBands() {
|
|
11373
|
+
return {
|
|
11374
|
+
left64: new Float32Array(BANDS),
|
|
11375
|
+
right64: new Float32Array(BANDS),
|
|
11376
|
+
left32: new Float32Array(32),
|
|
11377
|
+
right32: new Float32Array(32),
|
|
11378
|
+
left16: new Float32Array(16),
|
|
11379
|
+
right16: new Float32Array(16),
|
|
11380
|
+
level: 0,
|
|
11381
|
+
silent: true
|
|
11382
|
+
};
|
|
11383
|
+
}
|
|
11384
|
+
function downsample(dst, src64) {
|
|
11385
|
+
const g = src64.length / dst.length;
|
|
11386
|
+
for (let i = 0; i < dst.length; i++) {
|
|
11387
|
+
let s = 0;
|
|
11388
|
+
const i0 = Math.floor(i * g);
|
|
11389
|
+
const i1 = Math.max(i0 + 1, Math.floor((i + 1) * g));
|
|
11390
|
+
for (let j = i0; j < i1; j++) s += src64[j];
|
|
11391
|
+
dst[i] = s / (i1 - i0);
|
|
11392
|
+
}
|
|
11393
|
+
}
|
|
11394
|
+
function fillFromByteFreq(out, bytes, sampleRate) {
|
|
11395
|
+
const n = bytes.length;
|
|
11396
|
+
const nyquist = sampleRate * 0.5;
|
|
11397
|
+
const fMin = 20;
|
|
11398
|
+
const fMax = Math.min(2e4, nyquist);
|
|
11399
|
+
let levelSum = 0;
|
|
11400
|
+
for (let b = 0; b < BANDS; b++) {
|
|
11401
|
+
const t0 = b / BANDS;
|
|
11402
|
+
const t1 = (b + 1) / BANDS;
|
|
11403
|
+
const loHz = fMin * Math.pow(fMax / fMin, t0);
|
|
11404
|
+
const hiHz = fMin * Math.pow(fMax / fMin, t1);
|
|
11405
|
+
const i0 = Math.max(0, Math.floor(loHz / nyquist * n));
|
|
11406
|
+
const i1 = Math.min(n, Math.max(i0 + 1, Math.ceil(hiHz / nyquist * n)));
|
|
11407
|
+
let s = 0;
|
|
11408
|
+
for (let i = i0; i < i1; i++) s += bytes[i] / 255;
|
|
11409
|
+
const v = Math.min(1, s / (i1 - i0) * 1.35);
|
|
11410
|
+
out.left64[b] = v;
|
|
11411
|
+
out.right64[b] = v;
|
|
11412
|
+
if (b < 48) levelSum += v;
|
|
11413
|
+
}
|
|
11414
|
+
downsample(out.left32, out.left64);
|
|
11415
|
+
downsample(out.right32, out.right64);
|
|
11416
|
+
downsample(out.left16, out.left64);
|
|
11417
|
+
downsample(out.right16, out.right64);
|
|
11418
|
+
out.level = Math.min(1, levelSum / (48 * 1.2));
|
|
11419
|
+
out.silent = out.level < 0.02;
|
|
11420
|
+
}
|
|
11421
|
+
function hashHue(s) {
|
|
11422
|
+
let h = 2166136261;
|
|
11423
|
+
for (let i = 0; i < s.length; i++) {
|
|
11424
|
+
h ^= s.charCodeAt(i);
|
|
11425
|
+
h = Math.imul(h, 16777619);
|
|
11426
|
+
}
|
|
11427
|
+
return (h >>> 0) % 360;
|
|
11428
|
+
}
|
|
11429
|
+
function hslToRgb(h, sat, light) {
|
|
11430
|
+
const s = sat / 100;
|
|
11431
|
+
const l = light / 100;
|
|
11432
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
11433
|
+
const hp = h / 60;
|
|
11434
|
+
const x = c * (1 - Math.abs(hp % 2 - 1));
|
|
11435
|
+
let r = 0, g = 0, b = 0;
|
|
11436
|
+
if (hp < 1) [r, g, b] = [c, x, 0];
|
|
11437
|
+
else if (hp < 2) [r, g, b] = [x, c, 0];
|
|
11438
|
+
else if (hp < 3) [r, g, b] = [0, c, x];
|
|
11439
|
+
else if (hp < 4) [r, g, b] = [0, x, c];
|
|
11440
|
+
else if (hp < 5) [r, g, b] = [x, 0, c];
|
|
11441
|
+
else [r, g, b] = [c, 0, x];
|
|
11442
|
+
const m = l - c / 2;
|
|
11443
|
+
return [r + m, g + m, b + m];
|
|
11444
|
+
}
|
|
11445
|
+
function applyPalette(snap, seed) {
|
|
11446
|
+
const hue = hashHue(seed || "empty");
|
|
11447
|
+
const [pr, pg, pb] = hslToRgb(hue, 72, 48);
|
|
11448
|
+
const [sr, sg, sb] = hslToRgb((hue + 40) % 360, 55, 28);
|
|
11449
|
+
const [tr, tg, tb] = hslToRgb((hue + 20) % 360, 70, 72);
|
|
11450
|
+
snap.primaryColor = media.mediaVec3(pr, pg, pb);
|
|
11451
|
+
snap.secondaryColor = media.mediaVec3(sr, sg, sb);
|
|
11452
|
+
snap.tertiaryColor = media.mediaVec3(tr, tg, tb);
|
|
11453
|
+
snap.textColor = media.mediaVec3(0.98, 0.98, 1);
|
|
11454
|
+
snap.highContrastColor = media.mediaVec3(1, 1, 1);
|
|
11455
|
+
snap.hasThumbnail = !!seed;
|
|
11456
|
+
}
|
|
11457
|
+
function sampleArtworkPalette(img, w, h) {
|
|
11458
|
+
const c = document.createElement("canvas");
|
|
11459
|
+
c.width = 32;
|
|
11460
|
+
c.height = 32;
|
|
11461
|
+
const ctx = c.getContext("2d", { willReadFrequently: true });
|
|
11462
|
+
if (!ctx) return null;
|
|
11463
|
+
ctx.drawImage(img, 0, 0, w, h, 0, 0, 32, 32);
|
|
11464
|
+
const data = ctx.getImageData(0, 0, 32, 32).data;
|
|
11465
|
+
let r = 0, g = 0, b = 0, n = 0;
|
|
11466
|
+
let br = 0, bg = 0, bb = 0, best = -1;
|
|
11467
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
11468
|
+
const pr = data[i] / 255, pg = data[i + 1] / 255, pb = data[i + 2] / 255;
|
|
11469
|
+
r += pr;
|
|
11470
|
+
g += pg;
|
|
11471
|
+
b += pb;
|
|
11472
|
+
n++;
|
|
11473
|
+
const mx = Math.max(pr, pg, pb), mn = Math.min(pr, pg, pb);
|
|
11474
|
+
const sat = mx - mn;
|
|
11475
|
+
const lum = 0.2126 * pr + 0.7152 * pg + 0.0722 * pb;
|
|
11476
|
+
const score = sat * 1.4 + (lum > 0.15 && lum < 0.85 ? 0.3 : 0);
|
|
11477
|
+
if (score > best) {
|
|
11478
|
+
best = score;
|
|
11479
|
+
br = pr;
|
|
11480
|
+
bg = pg;
|
|
11481
|
+
bb = pb;
|
|
11482
|
+
}
|
|
11483
|
+
}
|
|
11484
|
+
if (!n) return null;
|
|
11485
|
+
const primary = [br, bg, bb];
|
|
11486
|
+
const secondary = [r / n * 0.55, g / n * 0.55, b / n * 0.55];
|
|
11487
|
+
const tertiary = [
|
|
11488
|
+
Math.min(1, primary[0] * 0.45 + 0.55),
|
|
11489
|
+
Math.min(1, primary[1] * 0.45 + 0.55),
|
|
11490
|
+
Math.min(1, primary[2] * 0.45 + 0.55)
|
|
11491
|
+
];
|
|
11492
|
+
return { primary, secondary, tertiary };
|
|
11493
|
+
}
|
|
11494
|
+
function rasterizeArtwork(img, srcW, srcH, size = 512) {
|
|
11495
|
+
const c = document.createElement("canvas");
|
|
11496
|
+
c.width = size;
|
|
11497
|
+
c.height = size;
|
|
11498
|
+
const ctx = c.getContext("2d");
|
|
11499
|
+
const scale = Math.max(size / Math.max(1, srcW), size / Math.max(1, srcH));
|
|
11500
|
+
const dw = srcW * scale;
|
|
11501
|
+
const dh = srcH * scale;
|
|
11502
|
+
ctx.fillStyle = "#000";
|
|
11503
|
+
ctx.fillRect(0, 0, size, size);
|
|
11504
|
+
ctx.drawImage(img, (size - dw) / 2, (size - dh) / 2, dw, dh);
|
|
11505
|
+
const id = ctx.getImageData(0, 0, size, size);
|
|
11506
|
+
return { width: size, height: size, rgba: new Uint8Array(id.data) };
|
|
11507
|
+
}
|
|
11508
|
+
function emptyMediaSnapshot() {
|
|
11509
|
+
return {
|
|
11510
|
+
hasMedia: false,
|
|
11511
|
+
state: MEDIA_PLAYBACK.STOPPED,
|
|
11512
|
+
title: "",
|
|
11513
|
+
artist: "",
|
|
11514
|
+
album: "",
|
|
11515
|
+
albumArtist: "",
|
|
11516
|
+
position: 0,
|
|
11517
|
+
duration: 0,
|
|
11518
|
+
hasThumbnail: false,
|
|
11519
|
+
primaryColor: media.mediaVec3(0, 0, 0),
|
|
11520
|
+
secondaryColor: media.mediaVec3(0, 0, 0),
|
|
11521
|
+
tertiaryColor: media.mediaVec3(0, 0, 0),
|
|
11522
|
+
textColor: media.mediaVec3(1, 1, 1),
|
|
11523
|
+
highContrastColor: media.mediaVec3(1, 1, 1),
|
|
11524
|
+
trackIndex: -1,
|
|
11525
|
+
lyrics: [],
|
|
11526
|
+
lyricLine: "",
|
|
11527
|
+
lyricIndex: -1
|
|
11528
|
+
};
|
|
11529
|
+
}
|
|
11530
|
+
async function openMicAnalyser() {
|
|
11531
|
+
if (!navigator.mediaDevices?.getUserMedia) return null;
|
|
11532
|
+
try {
|
|
11533
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
11534
|
+
audio: {
|
|
11535
|
+
echoCancellation: false,
|
|
11536
|
+
noiseSuppression: false,
|
|
11537
|
+
autoGainControl: false
|
|
11538
|
+
},
|
|
11539
|
+
video: false
|
|
11540
|
+
});
|
|
11541
|
+
const ctx = new AudioContext();
|
|
11542
|
+
const src = ctx.createMediaStreamSource(stream);
|
|
11543
|
+
const analyser = ctx.createAnalyser();
|
|
11544
|
+
analyser.fftSize = 2048;
|
|
11545
|
+
analyser.smoothingTimeConstant = 0.8;
|
|
11546
|
+
src.connect(analyser);
|
|
11547
|
+
if (ctx.state === "suspended") await ctx.resume().catch(() => {
|
|
11548
|
+
});
|
|
11549
|
+
return { ctx, stream, analyser, buf: new Uint8Array(analyser.frequencyBinCount) };
|
|
11550
|
+
} catch {
|
|
11551
|
+
return null;
|
|
11552
|
+
}
|
|
11553
|
+
}
|
|
11554
|
+
async function startLiveSystem(opts) {
|
|
11555
|
+
const origin = opts?.origin ?? (typeof location !== "undefined" ? location.origin : "");
|
|
11556
|
+
const onArtwork = opts?.onArtwork;
|
|
11557
|
+
const audioSnap = zeroBands();
|
|
11558
|
+
const mediaSnap = emptyMediaSnapshot();
|
|
11559
|
+
const winSnap = { app: "", title: "", url: "", index: 0 };
|
|
11560
|
+
let audioMode = "off";
|
|
11561
|
+
let mediaMode = "offline";
|
|
11562
|
+
let windowMode = "offline";
|
|
11563
|
+
let trackKey = "";
|
|
11564
|
+
let hasArtwork = false;
|
|
11565
|
+
let lastArtworkKey = "";
|
|
11566
|
+
const mic = await openMicAnalyser();
|
|
11567
|
+
if (mic) audioMode = "mic";
|
|
11568
|
+
else if (!navigator.mediaDevices?.getUserMedia) audioMode = "unavailable";
|
|
11569
|
+
else audioMode = "denied";
|
|
11570
|
+
let es = null;
|
|
11571
|
+
let pollTimer = null;
|
|
11572
|
+
let disposed = false;
|
|
11573
|
+
const requestArtwork = (key, title, artist) => {
|
|
11574
|
+
if (!origin || !onArtwork || !hasArtwork) return;
|
|
11575
|
+
if (lastArtworkKey === key) return;
|
|
11576
|
+
lastArtworkKey = key;
|
|
11577
|
+
onArtwork({
|
|
11578
|
+
url: `${origin}/api/system/artwork?k=${encodeURIComponent(key)}&_=${Date.now()}`,
|
|
11579
|
+
trackKey: key,
|
|
11580
|
+
title,
|
|
11581
|
+
artist
|
|
11582
|
+
});
|
|
11583
|
+
};
|
|
11584
|
+
const applyMediaPayload = (m) => {
|
|
11585
|
+
if (!m || !m.hasMedia) {
|
|
11586
|
+
mediaSnap.hasMedia = false;
|
|
11587
|
+
mediaSnap.state = MEDIA_PLAYBACK.STOPPED;
|
|
11588
|
+
mediaSnap.title = "";
|
|
11589
|
+
mediaSnap.artist = "";
|
|
11590
|
+
mediaSnap.album = "";
|
|
11591
|
+
mediaSnap.albumArtist = "";
|
|
11592
|
+
mediaSnap.position = 0;
|
|
11593
|
+
mediaSnap.duration = 0;
|
|
11594
|
+
mediaSnap.hasThumbnail = false;
|
|
11595
|
+
mediaSnap.trackIndex = -1;
|
|
11596
|
+
mediaMode = m ? "empty" : "offline";
|
|
11597
|
+
trackKey = "";
|
|
11598
|
+
hasArtwork = false;
|
|
11599
|
+
lastArtworkKey = "";
|
|
11600
|
+
return;
|
|
11601
|
+
}
|
|
11602
|
+
mediaMode = "live";
|
|
11603
|
+
mediaSnap.hasMedia = true;
|
|
11604
|
+
mediaSnap.state = Number(m.state) === 2 ? 2 : Number(m.state) === 1 ? 1 : 0;
|
|
11605
|
+
mediaSnap.title = String(m.title ?? "");
|
|
11606
|
+
mediaSnap.artist = String(m.artist ?? "");
|
|
11607
|
+
mediaSnap.album = String(m.album ?? "");
|
|
11608
|
+
mediaSnap.albumArtist = String(m.albumArtist ?? m.artist ?? "");
|
|
11609
|
+
mediaSnap.position = Number(m.position) || 0;
|
|
11610
|
+
mediaSnap.duration = Number(m.duration) || 0;
|
|
11611
|
+
hasArtwork = m.hasArtwork === true;
|
|
11612
|
+
const key = `${mediaSnap.title}|${mediaSnap.artist}|${mediaSnap.album}`;
|
|
11613
|
+
if (key !== trackKey) {
|
|
11614
|
+
trackKey = key;
|
|
11615
|
+
lastArtworkKey = "";
|
|
11616
|
+
mediaSnap.trackIndex = mediaSnap.trackIndex + 1 | 0;
|
|
11617
|
+
applyPalette(mediaSnap, key);
|
|
11618
|
+
requestArtwork(key, mediaSnap.title, mediaSnap.artist);
|
|
11619
|
+
} else {
|
|
11620
|
+
requestArtwork(key, mediaSnap.title, mediaSnap.artist);
|
|
11621
|
+
}
|
|
11622
|
+
};
|
|
11623
|
+
const applyWindowPayload = (w) => {
|
|
11624
|
+
if (!w) {
|
|
11625
|
+
windowMode = "offline";
|
|
11626
|
+
return;
|
|
11627
|
+
}
|
|
11628
|
+
winSnap.app = String(w.app ?? "");
|
|
11629
|
+
winSnap.title = String(w.title ?? "");
|
|
11630
|
+
winSnap.url = String(w.url ?? "");
|
|
11631
|
+
windowMode = winSnap.app || winSnap.title ? "live" : "empty";
|
|
11632
|
+
};
|
|
11633
|
+
const pollOnce = async () => {
|
|
11634
|
+
if (!origin || disposed) return;
|
|
11635
|
+
try {
|
|
11636
|
+
const [mr, wr] = await Promise.all([
|
|
11637
|
+
fetch(`${origin}/api/system/media`, { cache: "no-store" }),
|
|
11638
|
+
fetch(`${origin}/api/system/window`, { cache: "no-store" })
|
|
11639
|
+
]);
|
|
11640
|
+
if (mr.ok) {
|
|
11641
|
+
const j = await mr.json();
|
|
11642
|
+
applyMediaPayload(j);
|
|
11643
|
+
} else {
|
|
11644
|
+
mediaMode = "offline";
|
|
11645
|
+
}
|
|
11646
|
+
if (wr.ok) {
|
|
11647
|
+
applyWindowPayload(await wr.json());
|
|
11648
|
+
}
|
|
11649
|
+
} catch {
|
|
11650
|
+
mediaMode = mediaMode === "live" ? "live" : "offline";
|
|
11651
|
+
windowMode = windowMode === "live" ? "live" : "offline";
|
|
11652
|
+
}
|
|
11653
|
+
};
|
|
11654
|
+
if (origin) {
|
|
11655
|
+
await pollOnce();
|
|
11656
|
+
pollTimer = setInterval(() => void pollOnce(), 1e3);
|
|
11657
|
+
try {
|
|
11658
|
+
es = new EventSource(`${origin}/api/system/stream`);
|
|
11659
|
+
es.onmessage = (ev) => {
|
|
11660
|
+
if (disposed) return;
|
|
11661
|
+
try {
|
|
11662
|
+
const data = JSON.parse(ev.data);
|
|
11663
|
+
applyMediaPayload(data.media);
|
|
11664
|
+
applyWindowPayload(data.window);
|
|
11665
|
+
} catch {
|
|
11666
|
+
}
|
|
11667
|
+
};
|
|
11668
|
+
} catch {
|
|
11669
|
+
}
|
|
11670
|
+
}
|
|
11671
|
+
const postControl = (action) => {
|
|
11672
|
+
if (!origin || disposed) return;
|
|
11673
|
+
void fetch(`${origin}/api/system/media-control`, {
|
|
11674
|
+
method: "POST",
|
|
11675
|
+
headers: { "Content-Type": "application/json" },
|
|
11676
|
+
body: JSON.stringify({ action })
|
|
11677
|
+
}).then(async (r) => {
|
|
11678
|
+
if (!r.ok) return null;
|
|
11679
|
+
return r.json();
|
|
11680
|
+
}).then((j) => {
|
|
11681
|
+
if (j && typeof j === "object") applyMediaPayload(j);
|
|
11682
|
+
void pollOnce();
|
|
11683
|
+
}).catch(() => {
|
|
11684
|
+
void pollOnce();
|
|
11685
|
+
});
|
|
11686
|
+
};
|
|
11687
|
+
return {
|
|
11688
|
+
audio: {
|
|
11689
|
+
snapshot: audioSnap,
|
|
11690
|
+
pump: () => {
|
|
11691
|
+
if (!mic || disposed) {
|
|
11692
|
+
audioSnap.level = 0;
|
|
11693
|
+
audioSnap.silent = true;
|
|
11694
|
+
return;
|
|
11695
|
+
}
|
|
11696
|
+
mic.analyser.getByteFrequencyData(mic.buf);
|
|
11697
|
+
fillFromByteFreq(audioSnap, mic.buf, mic.ctx.sampleRate || 48e3);
|
|
11698
|
+
}
|
|
11699
|
+
},
|
|
11700
|
+
media: {
|
|
11701
|
+
snapshot: mediaSnap,
|
|
11702
|
+
pump: () => {
|
|
11703
|
+
},
|
|
11704
|
+
skipNext: () => postControl("skipNext"),
|
|
11705
|
+
skipPrevious: () => postControl("skipPrevious"),
|
|
11706
|
+
play: () => postControl("play"),
|
|
11707
|
+
pause: () => postControl("pause"),
|
|
11708
|
+
playPause: () => postControl("playPause")
|
|
11709
|
+
},
|
|
11710
|
+
windowTitle: {
|
|
11711
|
+
snapshot: winSnap,
|
|
11712
|
+
pump: () => {
|
|
11713
|
+
}
|
|
11714
|
+
},
|
|
11715
|
+
status: () => ({
|
|
11716
|
+
audio: audioMode,
|
|
11717
|
+
media: mediaMode,
|
|
11718
|
+
window: windowMode,
|
|
11719
|
+
title: mediaSnap.title,
|
|
11720
|
+
artist: mediaSnap.artist,
|
|
11721
|
+
app: winSnap.app,
|
|
11722
|
+
windowTitle: winSnap.title,
|
|
11723
|
+
hasArtwork
|
|
11724
|
+
}),
|
|
11725
|
+
dispose: () => {
|
|
11726
|
+
disposed = true;
|
|
11727
|
+
if (pollTimer) {
|
|
11728
|
+
clearInterval(pollTimer);
|
|
11729
|
+
pollTimer = null;
|
|
11730
|
+
}
|
|
11731
|
+
try {
|
|
11732
|
+
es?.close();
|
|
11733
|
+
} catch {
|
|
11734
|
+
}
|
|
11735
|
+
es = null;
|
|
11736
|
+
if (mic) {
|
|
11737
|
+
try {
|
|
11738
|
+
mic.stream.getTracks().forEach((t) => t.stop());
|
|
11739
|
+
void mic.ctx.close();
|
|
11740
|
+
} catch {
|
|
11741
|
+
}
|
|
11742
|
+
}
|
|
11743
|
+
}
|
|
11744
|
+
};
|
|
11745
|
+
}
|
|
11152
11746
|
const WE_SHADER_HEADERS = {
|
|
11153
11747
|
"common.h": `// WE common.h(重建子集,供 we-scene 浏览器渲染)
|
|
11154
11748
|
#define M_PI 3.14159265359
|
|
@@ -11421,10 +12015,18 @@ mat3 squareToQuad(vec2 p0, vec2 p1, vec2 p2, vec2 p3) {
|
|
|
11421
12015
|
float d = p1.y - p0.y + g * p1.y;
|
|
11422
12016
|
float e = p3.y - p0.y + h * p3.y;
|
|
11423
12017
|
float f = p0.y;
|
|
11424
|
-
//
|
|
11425
|
-
|
|
11426
|
-
|
|
11427
|
-
|
|
12018
|
+
// 调用点一律是 mul(vec3(uv,1), inverse(本函数结果)),hlsl2glsl 把它转写成
|
|
12019
|
+
// transpose(xform) * vec3(uv,1)。要让屏幕点 s 得到 texCoord = S⁻¹·s(S 为
|
|
12020
|
+
// Heckbert 正向矩阵 [[a,b,c],[d,e,f],[g,h,1]],单位方→四边形),必须
|
|
12021
|
+
// xform = inverse(本函数结果) 满足 transpose(xform)·s = S⁻¹·s,
|
|
12022
|
+
// 即本函数返回 S 的**转置**:mat3 列主序构造为 (a,d,g)(b,e,h)(c,f,1) 的转置
|
|
12023
|
+
// = (a,b,c)(d,e,f)(g,h,1)。排布差一个转置,perspective/水波等全部错位——
|
|
12024
|
+
// 3174556087 的音谱柱被贴到窗户侧边竖排(应为贴下窗沿横排)实测确认。
|
|
12025
|
+
// 2026-09-05 数值模拟:旧排布 transpose(S⁻¹)·corner 与正确 S⁻¹·corner 逐项不同,
|
|
12026
|
+
// 可见区塌缩成一条斜带;新排布后中心 (0.5,0.5) → (0.478,0.511) ∈ [0,1]²。
|
|
12027
|
+
return mat3(a, b, c,
|
|
12028
|
+
d, e, f,
|
|
12029
|
+
g, h, 1.0);
|
|
11428
12030
|
}
|
|
11429
12031
|
`,
|
|
11430
12032
|
// WE common_blur.h(重建)。blurNa 的权重不是估算的 —— 壁纸 1444077782 里存着
|
|
@@ -11554,6 +12156,66 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
11554
12156
|
"common_vertex.h": `// WE common_vertex.h(重建:空占位,见 headers.ts 注释)
|
|
11555
12157
|
`
|
|
11556
12158
|
};
|
|
12159
|
+
function sanitizeFontForBrowser(src) {
|
|
12160
|
+
if (!src || src.length < 12) return src;
|
|
12161
|
+
const b0 = src[0], b1 = src[1], b2 = src[2], b3 = src[3];
|
|
12162
|
+
const isTtf = b0 === 0 && b1 === 1 && b2 === 0 && b3 === 0;
|
|
12163
|
+
const isOtto = b0 === 79 && b1 === 84 && b2 === 84 && b3 === 79;
|
|
12164
|
+
if (!isTtf && !isOtto) return src;
|
|
12165
|
+
const out = Uint8Array.from(src);
|
|
12166
|
+
const dv = new DataView(out.buffer, out.byteOffset, out.byteLength);
|
|
12167
|
+
const numTables = dv.getUint16(4);
|
|
12168
|
+
if (numTables <= 0 || 12 + numTables * 16 > out.length) return src;
|
|
12169
|
+
let cmapEntry = -1;
|
|
12170
|
+
let cmapOffset = 0;
|
|
12171
|
+
let cmapLength = 0;
|
|
12172
|
+
for (let i = 0; i < numTables; i++) {
|
|
12173
|
+
const e = 12 + i * 16;
|
|
12174
|
+
const tag = String.fromCharCode(out[e], out[e + 1], out[e + 2], out[e + 3]);
|
|
12175
|
+
if (tag === "cmap") {
|
|
12176
|
+
cmapEntry = e;
|
|
12177
|
+
cmapOffset = dv.getUint32(e + 8);
|
|
12178
|
+
cmapLength = dv.getUint32(e + 12);
|
|
12179
|
+
break;
|
|
12180
|
+
}
|
|
12181
|
+
}
|
|
12182
|
+
if (cmapEntry < 0 || cmapOffset + cmapLength > out.length) return src;
|
|
12183
|
+
let changed = false;
|
|
12184
|
+
const numEnc = dv.getUint16(cmapOffset + 2);
|
|
12185
|
+
for (let i = 0; i < numEnc; i++) {
|
|
12186
|
+
const rec = cmapOffset + 4 + i * 8;
|
|
12187
|
+
const soff = dv.getUint32(rec + 4);
|
|
12188
|
+
const abs = cmapOffset + soff;
|
|
12189
|
+
if (abs + 14 > out.length) continue;
|
|
12190
|
+
if (dv.getUint16(abs) !== 4) continue;
|
|
12191
|
+
const segCountX2 = dv.getUint16(abs + 6);
|
|
12192
|
+
const segCount = segCountX2 >>> 1;
|
|
12193
|
+
if (segCount < 1) continue;
|
|
12194
|
+
const expSearch = 2 * Math.pow(2, Math.floor(Math.log2(segCount)));
|
|
12195
|
+
const expSel = Math.floor(Math.log2(segCount));
|
|
12196
|
+
const expShift = segCountX2 - expSearch;
|
|
12197
|
+
const curSearch = dv.getUint16(abs + 8);
|
|
12198
|
+
const curSel = dv.getUint16(abs + 10);
|
|
12199
|
+
const curShift = dv.getUint16(abs + 12);
|
|
12200
|
+
if (curSearch === expSearch && curSel === expSel && curShift === expShift) continue;
|
|
12201
|
+
dv.setUint16(abs + 8, expSearch);
|
|
12202
|
+
dv.setUint16(abs + 10, expSel);
|
|
12203
|
+
dv.setUint16(abs + 12, expShift);
|
|
12204
|
+
changed = true;
|
|
12205
|
+
}
|
|
12206
|
+
if (!changed) return src;
|
|
12207
|
+
let sum = 0;
|
|
12208
|
+
const end = cmapOffset + cmapLength;
|
|
12209
|
+
for (let p = cmapOffset; p < end; p += 4) {
|
|
12210
|
+
const b02 = out[p] || 0;
|
|
12211
|
+
const b12 = p + 1 < end ? out[p + 1] : 0;
|
|
12212
|
+
const b22 = p + 2 < end ? out[p + 2] : 0;
|
|
12213
|
+
const b32 = p + 3 < end ? out[p + 3] : 0;
|
|
12214
|
+
sum = sum + (b02 << 24 | b12 << 16 | b22 << 8 | b32) >>> 0;
|
|
12215
|
+
}
|
|
12216
|
+
dv.setUint32(cmapEntry + 4, sum);
|
|
12217
|
+
return out;
|
|
12218
|
+
}
|
|
11557
12219
|
const SYSTEM_FONT_FAMILIES = {
|
|
11558
12220
|
systemfont_segoe: "'Segoe UI', 'Helvetica Neue', Arial, sans-serif",
|
|
11559
12221
|
systemfont_arial: "Arial, 'Helvetica Neue', sans-serif",
|
|
@@ -11755,6 +12417,22 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
11755
12417
|
if (disposed) return;
|
|
11756
12418
|
const supportsAudioProcessing = project?.general?.supportsaudioprocessing !== false;
|
|
11757
12419
|
const simAudio = createSimulatedAudio();
|
|
12420
|
+
const simMedia = media.createSimulatedMedia();
|
|
12421
|
+
const simWindow = system.createSimulatedWindowTitle();
|
|
12422
|
+
let live = null;
|
|
12423
|
+
const liveHold = {
|
|
12424
|
+
mediaDriver: null,
|
|
12425
|
+
lastSnap: {
|
|
12426
|
+
get: () => null,
|
|
12427
|
+
setHasThumbnail: () => {
|
|
12428
|
+
}
|
|
12429
|
+
}
|
|
12430
|
+
};
|
|
12431
|
+
const audioDriverRef = {
|
|
12432
|
+
current: null
|
|
12433
|
+
};
|
|
12434
|
+
let mediaDriver = simMedia;
|
|
12435
|
+
let windowDriver = simWindow;
|
|
11758
12436
|
const audioSim = { enabled: supportsAudioProcessing };
|
|
11759
12437
|
const zero = (n) => new Float32Array(n);
|
|
11760
12438
|
const SILENT_AUDIO = {
|
|
@@ -11767,32 +12445,45 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
11767
12445
|
level: 0,
|
|
11768
12446
|
silent: true
|
|
11769
12447
|
};
|
|
11770
|
-
renderer.setAudioProvider(() =>
|
|
12448
|
+
renderer.setAudioProvider(() => {
|
|
12449
|
+
if (!audioSim.enabled) return SILENT_AUDIO;
|
|
12450
|
+
return audioDriverRef.current ? audioDriverRef.current.snapshot : simAudio.snapshot;
|
|
12451
|
+
});
|
|
11771
12452
|
const audioViews = /* @__PURE__ */ new Map();
|
|
11772
12453
|
window.__audioStats = () => ({
|
|
11773
12454
|
enabled: audioSim.enabled,
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
12455
|
+
live: !!audioDriverRef.current,
|
|
12456
|
+
level: audioSim.enabled ? Math.round((audioDriverRef.current ? audioDriverRef.current.snapshot : simAudio.snapshot).level * 1e3) / 1e3 : 0,
|
|
12457
|
+
silent: audioSim.enabled ? (audioDriverRef.current ? audioDriverRef.current.snapshot : simAudio.snapshot).silent : true,
|
|
12458
|
+
bass: audioSim.enabled ? Math.round((audioDriverRef.current ? audioDriverRef.current.snapshot : simAudio.snapshot).left64[2] * 1e3) / 1e3 : 0
|
|
11777
12459
|
});
|
|
11778
12460
|
window.__audioMute = (on) => {
|
|
11779
12461
|
audioSim.enabled = !on;
|
|
11780
12462
|
return audioSim.enabled;
|
|
11781
12463
|
};
|
|
11782
|
-
reportDiag(
|
|
11783
|
-
|
|
11784
|
-
|
|
12464
|
+
reportDiag(
|
|
12465
|
+
rt,
|
|
12466
|
+
cfg,
|
|
12467
|
+
`audio: ${audioDriverRef.current ? "live mic" : "simulated"} stream, supportsaudioprocessing=${supportsAudioProcessing}`
|
|
12468
|
+
);
|
|
11785
12469
|
const shortcuts = system.createShortcutHandler((name) => {
|
|
11786
12470
|
reportDiag(rt, cfg, `openUserShortcut: ${name}`);
|
|
11787
12471
|
});
|
|
11788
12472
|
const mediaSim = { enabled: true, override: null };
|
|
11789
12473
|
const mediaHooks = [];
|
|
11790
12474
|
let lastMediaSnap = null;
|
|
12475
|
+
liveHold.lastSnap = {
|
|
12476
|
+
get: () => lastMediaSnap,
|
|
12477
|
+
setHasThumbnail: (v) => {
|
|
12478
|
+
if (lastMediaSnap) lastMediaSnap.hasThumbnail = v;
|
|
12479
|
+
}
|
|
12480
|
+
};
|
|
12481
|
+
const mediaSnapshot = () => mediaDriver.snapshot;
|
|
11791
12482
|
const registerMediaHook = (sb) => {
|
|
11792
12483
|
if (!sb || !sb.hasMediaHook || mediaHooks.includes(sb)) return;
|
|
11793
12484
|
mediaHooks.push(sb);
|
|
11794
|
-
if (!
|
|
11795
|
-
for (const { name, event } of media.diffMediaEvents(null,
|
|
12485
|
+
if (!mediaSnapshot().hasMedia) return;
|
|
12486
|
+
for (const { name, event } of media.diffMediaEvents(null, mediaSnapshot())) {
|
|
11796
12487
|
try {
|
|
11797
12488
|
sb.callMedia(name, event);
|
|
11798
12489
|
} catch {
|
|
@@ -11801,26 +12492,27 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
11801
12492
|
};
|
|
11802
12493
|
window.__mediaStats = () => ({
|
|
11803
12494
|
enabled: mediaSim.enabled,
|
|
12495
|
+
live: !!live,
|
|
11804
12496
|
hooks: mediaHooks.length,
|
|
11805
|
-
title:
|
|
11806
|
-
artist:
|
|
11807
|
-
album:
|
|
11808
|
-
state:
|
|
11809
|
-
position: Math.round(
|
|
11810
|
-
duration:
|
|
11811
|
-
hasThumbnail:
|
|
11812
|
-
lyric:
|
|
11813
|
-
primaryColor:
|
|
12497
|
+
title: mediaSnapshot().title,
|
|
12498
|
+
artist: mediaSnapshot().artist,
|
|
12499
|
+
album: mediaSnapshot().album,
|
|
12500
|
+
state: mediaSnapshot().state,
|
|
12501
|
+
position: Math.round(mediaSnapshot().position),
|
|
12502
|
+
duration: mediaSnapshot().duration,
|
|
12503
|
+
hasThumbnail: mediaSnapshot().hasThumbnail,
|
|
12504
|
+
lyric: mediaSnapshot().lyricLine,
|
|
12505
|
+
primaryColor: mediaSnapshot().primaryColor ? [mediaSnapshot().primaryColor.x, mediaSnapshot().primaryColor.y, mediaSnapshot().primaryColor.z] : null
|
|
11814
12506
|
});
|
|
11815
12507
|
window.__mediaSet = (patch) => {
|
|
11816
|
-
Object.assign(
|
|
11817
|
-
const evts = media.diffMediaEvents(lastMediaSnap,
|
|
12508
|
+
Object.assign(mediaSnapshot(), patch || {});
|
|
12509
|
+
const evts = media.diffMediaEvents(lastMediaSnap, mediaSnapshot());
|
|
11818
12510
|
for (const { name, event } of evts) for (const sb of mediaHooks) sb.callMedia(name, event);
|
|
11819
|
-
lastMediaSnap = media.cloneMediaSnapshot(
|
|
12511
|
+
lastMediaSnap = media.cloneMediaSnapshot(mediaSnapshot());
|
|
11820
12512
|
return window.__mediaStats();
|
|
11821
12513
|
};
|
|
11822
12514
|
const dispatchMediaNow = () => {
|
|
11823
|
-
const evts = media.diffMediaEvents(lastMediaSnap,
|
|
12515
|
+
const evts = media.diffMediaEvents(lastMediaSnap, mediaSnapshot());
|
|
11824
12516
|
for (const { name, event } of evts) {
|
|
11825
12517
|
for (const sb of mediaHooks) {
|
|
11826
12518
|
try {
|
|
@@ -11829,42 +12521,50 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
11829
12521
|
}
|
|
11830
12522
|
}
|
|
11831
12523
|
}
|
|
11832
|
-
lastMediaSnap = media.cloneMediaSnapshot(
|
|
12524
|
+
lastMediaSnap = media.cloneMediaSnapshot(mediaSnapshot());
|
|
11833
12525
|
};
|
|
11834
12526
|
const mediaControl = {
|
|
11835
|
-
snapshot
|
|
12527
|
+
get snapshot() {
|
|
12528
|
+
return mediaSnapshot();
|
|
12529
|
+
},
|
|
11836
12530
|
skipNext: () => {
|
|
11837
|
-
|
|
12531
|
+
mediaDriver.skipNext();
|
|
11838
12532
|
dispatchMediaNow();
|
|
11839
|
-
return
|
|
12533
|
+
return mediaSnapshot();
|
|
11840
12534
|
},
|
|
11841
12535
|
skipPrevious: () => {
|
|
11842
|
-
|
|
12536
|
+
mediaDriver.skipPrevious();
|
|
11843
12537
|
dispatchMediaNow();
|
|
11844
|
-
return
|
|
12538
|
+
return mediaSnapshot();
|
|
11845
12539
|
},
|
|
11846
12540
|
play: () => {
|
|
11847
|
-
|
|
12541
|
+
mediaDriver.play();
|
|
11848
12542
|
dispatchMediaNow();
|
|
11849
|
-
return
|
|
12543
|
+
return mediaSnapshot();
|
|
11850
12544
|
},
|
|
11851
12545
|
pause: () => {
|
|
11852
|
-
|
|
12546
|
+
mediaDriver.pause();
|
|
11853
12547
|
dispatchMediaNow();
|
|
11854
|
-
return
|
|
12548
|
+
return mediaSnapshot();
|
|
11855
12549
|
},
|
|
11856
12550
|
playPause: () => {
|
|
11857
|
-
|
|
12551
|
+
mediaDriver.playPause();
|
|
11858
12552
|
dispatchMediaNow();
|
|
11859
|
-
return
|
|
12553
|
+
return mediaSnapshot();
|
|
11860
12554
|
}
|
|
11861
12555
|
};
|
|
11862
12556
|
window.__mediaControl = mediaControl;
|
|
11863
12557
|
window.__system = {
|
|
11864
12558
|
media: mediaControl,
|
|
11865
|
-
windowTitle:
|
|
11866
|
-
shortcuts: shortcuts.last
|
|
12559
|
+
windowTitle: windowDriver.snapshot,
|
|
12560
|
+
shortcuts: shortcuts.last,
|
|
12561
|
+
live: null
|
|
11867
12562
|
};
|
|
12563
|
+
window.__liveSystem = () => ({
|
|
12564
|
+
audio: "off",
|
|
12565
|
+
media: "offline",
|
|
12566
|
+
window: "offline"
|
|
12567
|
+
});
|
|
11868
12568
|
const pointerSrc = pointerLib.createPointerSource(
|
|
11869
12569
|
cfg.canvas ? {
|
|
11870
12570
|
target: cfg.canvas,
|
|
@@ -11968,6 +12668,118 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
11968
12668
|
textures.set("$mediaPreviousThumbnail", mkThumb(tracks[tracks.length - 1]));
|
|
11969
12669
|
}
|
|
11970
12670
|
}
|
|
12671
|
+
if (cfg.liveSystem) {
|
|
12672
|
+
try {
|
|
12673
|
+
const uploadLiveArtwork = async (info) => {
|
|
12674
|
+
try {
|
|
12675
|
+
const res = await fetch(info.url, { cache: "no-store" });
|
|
12676
|
+
if (!res.ok) return;
|
|
12677
|
+
const blob = await res.blob();
|
|
12678
|
+
const bmp = await createImageBitmap(blob);
|
|
12679
|
+
const raster = rasterizeArtwork(bmp, bmp.width, bmp.height, 512);
|
|
12680
|
+
const palette = sampleArtworkPalette(bmp, bmp.width, bmp.height);
|
|
12681
|
+
bmp.close?.();
|
|
12682
|
+
const cur = textures.get("$mediaThumbnail");
|
|
12683
|
+
if (cur) textures.set("$mediaPreviousThumbnail", cur);
|
|
12684
|
+
const gl = renderer.gl;
|
|
12685
|
+
const existing = textures.get("$mediaThumbnail");
|
|
12686
|
+
if (existing?.glTex) {
|
|
12687
|
+
gl.bindTexture(gl.TEXTURE_2D, existing.glTex);
|
|
12688
|
+
gl.texImage2D(
|
|
12689
|
+
gl.TEXTURE_2D,
|
|
12690
|
+
0,
|
|
12691
|
+
gl.RGBA,
|
|
12692
|
+
raster.width,
|
|
12693
|
+
raster.height,
|
|
12694
|
+
0,
|
|
12695
|
+
gl.RGBA,
|
|
12696
|
+
gl.UNSIGNED_BYTE,
|
|
12697
|
+
raster.rgba
|
|
12698
|
+
);
|
|
12699
|
+
existing.width = raster.width;
|
|
12700
|
+
existing.height = raster.height;
|
|
12701
|
+
existing.mips = [raster];
|
|
12702
|
+
} else {
|
|
12703
|
+
textures.set("$mediaThumbnail", {
|
|
12704
|
+
glTex: rnd.makeTextureMip(gl, [raster], false),
|
|
12705
|
+
width: raster.width,
|
|
12706
|
+
height: raster.height,
|
|
12707
|
+
rg88: false,
|
|
12708
|
+
mips: [raster],
|
|
12709
|
+
generated: true
|
|
12710
|
+
});
|
|
12711
|
+
}
|
|
12712
|
+
const snap = mediaDriver.snapshot;
|
|
12713
|
+
if (palette) {
|
|
12714
|
+
snap.primaryColor = media.mediaVec3(...palette.primary);
|
|
12715
|
+
snap.secondaryColor = media.mediaVec3(...palette.secondary);
|
|
12716
|
+
snap.tertiaryColor = media.mediaVec3(...palette.tertiary);
|
|
12717
|
+
snap.textColor = media.mediaVec3(0.98, 0.98, 1);
|
|
12718
|
+
snap.highContrastColor = media.mediaVec3(1, 1, 1);
|
|
12719
|
+
}
|
|
12720
|
+
snap.hasThumbnail = true;
|
|
12721
|
+
liveHold.lastSnap.setHasThumbnail(false);
|
|
12722
|
+
reportDiag(rt, cfg, `liveSystem: artwork ${info.title || info.trackKey}`);
|
|
12723
|
+
} catch (e) {
|
|
12724
|
+
reportDiag(
|
|
12725
|
+
rt,
|
|
12726
|
+
cfg,
|
|
12727
|
+
`liveSystem: artwork 失败 (${e instanceof Error ? e.message : e})`
|
|
12728
|
+
);
|
|
12729
|
+
}
|
|
12730
|
+
};
|
|
12731
|
+
live = await startLiveSystem({
|
|
12732
|
+
origin: location.origin,
|
|
12733
|
+
onArtwork: (info) => {
|
|
12734
|
+
void uploadLiveArtwork(info);
|
|
12735
|
+
}
|
|
12736
|
+
});
|
|
12737
|
+
mediaDriver = live.media;
|
|
12738
|
+
windowDriver = live.windowTitle;
|
|
12739
|
+
liveHold.mediaDriver = live.media;
|
|
12740
|
+
if (live.status().audio === "mic") audioDriverRef.current = live.audio;
|
|
12741
|
+
if (mediaDriver.snapshot.hasMedia) {
|
|
12742
|
+
for (const { name, event } of media.diffMediaEvents(null, mediaDriver.snapshot)) {
|
|
12743
|
+
for (const sb of mediaHooks) {
|
|
12744
|
+
try {
|
|
12745
|
+
sb.callMedia(name, event);
|
|
12746
|
+
} catch {
|
|
12747
|
+
}
|
|
12748
|
+
}
|
|
12749
|
+
}
|
|
12750
|
+
lastMediaSnap = media.cloneMediaSnapshot(mediaDriver.snapshot);
|
|
12751
|
+
}
|
|
12752
|
+
const st = live.status();
|
|
12753
|
+
reportDiag(
|
|
12754
|
+
rt,
|
|
12755
|
+
cfg,
|
|
12756
|
+
`liveSystem: audio=${st.audio} media=${st.media} window=${st.window}` + (st.title ? ` title="${st.title}"` : "") + (st.hasArtwork ? " artwork=1" : "")
|
|
12757
|
+
);
|
|
12758
|
+
reportDiag(
|
|
12759
|
+
rt,
|
|
12760
|
+
cfg,
|
|
12761
|
+
`audio: ${audioDriverRef.current ? "live mic" : "simulated"} stream, supportsaudioprocessing=${supportsAudioProcessing}`
|
|
12762
|
+
);
|
|
12763
|
+
window.__system = {
|
|
12764
|
+
media: mediaControl,
|
|
12765
|
+
windowTitle: windowDriver.snapshot,
|
|
12766
|
+
shortcuts: shortcuts.last,
|
|
12767
|
+
live: () => live.status()
|
|
12768
|
+
};
|
|
12769
|
+
window.__liveSystem = () => live.status();
|
|
12770
|
+
} catch (e) {
|
|
12771
|
+
reportDiag(rt, cfg, `liveSystem: 启动失败,回退模拟源 (${e instanceof Error ? e.message : e})`);
|
|
12772
|
+
live = null;
|
|
12773
|
+
}
|
|
12774
|
+
}
|
|
12775
|
+
{
|
|
12776
|
+
const prevCleanup = particleCleanup;
|
|
12777
|
+
particleCleanup = () => {
|
|
12778
|
+
prevCleanup?.();
|
|
12779
|
+
live?.dispose();
|
|
12780
|
+
live = null;
|
|
12781
|
+
};
|
|
12782
|
+
}
|
|
11971
12783
|
const texInflight = /* @__PURE__ */ new Map();
|
|
11972
12784
|
const loadTexInner = async (name) => {
|
|
11973
12785
|
if (textures.has(name)) return textures.get(name);
|
|
@@ -12159,6 +12971,13 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
12159
12971
|
model = JSON.parse(readText(modelEntry));
|
|
12160
12972
|
}
|
|
12161
12973
|
scn.applySolidFromModel(layer, model);
|
|
12974
|
+
const instUt = layer.srcObject?.instance?.usertextures;
|
|
12975
|
+
const instUtName = instUt?.[0] && typeof instUt[0].name === "string" && instUt[0].name.startsWith("$") ? instUt[0].name : null;
|
|
12976
|
+
const instBoundTex = instUtName && textures.has(instUtName) ? instUtName : null;
|
|
12977
|
+
if (instBoundTex) {
|
|
12978
|
+
layer.textureName = instBoundTex;
|
|
12979
|
+
layer.solid = false;
|
|
12980
|
+
}
|
|
12162
12981
|
if (model && typeof model === "object" && "width" in model && "height" in model) {
|
|
12163
12982
|
const m = model;
|
|
12164
12983
|
if ((layer.size?.[0] || 0) === 0 && (layer.size?.[1] || 0) === 0 && m.width > 0 && m.height > 0) {
|
|
@@ -12199,7 +13018,7 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
12199
13018
|
texJobs.push(
|
|
12200
13019
|
loadTex(tn).then((entry) => {
|
|
12201
13020
|
if (!entry) return;
|
|
12202
|
-
if (si === 0) {
|
|
13021
|
+
if (si === 0 && !instBoundTex) {
|
|
12203
13022
|
layer.textureName = tn;
|
|
12204
13023
|
loadedTex++;
|
|
12205
13024
|
if (entry.videoCtl) layer.videoCtl = entry.videoCtl;
|
|
@@ -12270,7 +13089,10 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
12270
13089
|
height: gen.height,
|
|
12271
13090
|
rg88: false,
|
|
12272
13091
|
mips: [gen],
|
|
12273
|
-
generated: true
|
|
13092
|
+
generated: true,
|
|
13093
|
+
// 内置贴图的帧表(rain1/rain2 的 1×4 图集):randomframe 预设依赖它
|
|
13094
|
+
// 随机取帧,缺了就整图采样画出超长丝(1823900922)。
|
|
13095
|
+
frames: ptex.builtinParticleFrames(name) ?? void 0
|
|
12274
13096
|
};
|
|
12275
13097
|
textures.set(name, entry);
|
|
12276
13098
|
builtinTexCount++;
|
|
@@ -12476,8 +13298,8 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
12476
13298
|
if (particleDiagFrame < 2) {
|
|
12477
13299
|
particleDiagFrame++;
|
|
12478
13300
|
if (particleDiagFrame === 2) {
|
|
12479
|
-
const
|
|
12480
|
-
reportDiag(rt, cfg, `particles live: ${
|
|
13301
|
+
const live2 = particleSystems.reduce((s, ps) => s + ps.liveCount(), 0);
|
|
13302
|
+
reportDiag(rt, cfg, `particles live: ${live2} across ${particleSystems.length} systems`);
|
|
12481
13303
|
}
|
|
12482
13304
|
}
|
|
12483
13305
|
},
|
|
@@ -12496,7 +13318,7 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
12496
13318
|
`particles: ${particleSystems.length} systems, ${builtinTexCount} builtin tex generated`
|
|
12497
13319
|
);
|
|
12498
13320
|
window.__particleStats = () => particleSystems.map((ps) => {
|
|
12499
|
-
let
|
|
13321
|
+
let live2 = 0;
|
|
12500
13322
|
let minX = Infinity;
|
|
12501
13323
|
let maxX = -Infinity;
|
|
12502
13324
|
let minY = Infinity;
|
|
@@ -12505,7 +13327,7 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
12505
13327
|
let maxS = -Infinity;
|
|
12506
13328
|
for (const p of ps.pool) {
|
|
12507
13329
|
if (!p.alive) continue;
|
|
12508
|
-
|
|
13330
|
+
live2++;
|
|
12509
13331
|
const px = ps.originX + p.x * ps.scaleX;
|
|
12510
13332
|
const py = ps.originY + p.y * ps.scaleY;
|
|
12511
13333
|
if (px < minX) minX = px;
|
|
@@ -12517,13 +13339,13 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
12517
13339
|
if (s > maxS) maxS = s;
|
|
12518
13340
|
}
|
|
12519
13341
|
return {
|
|
12520
|
-
live,
|
|
13342
|
+
live: live2,
|
|
12521
13343
|
max: ps.maxCount,
|
|
12522
13344
|
blend: ps.blend,
|
|
12523
13345
|
renderer: ps.renderers.map((r) => r.kind).join("+"),
|
|
12524
13346
|
origin: [Math.round(ps.originX), Math.round(ps.originY)],
|
|
12525
|
-
bbox:
|
|
12526
|
-
size:
|
|
13347
|
+
bbox: live2 ? [Math.round(minX), Math.round(minY), Math.round(maxX), Math.round(maxY)] : null,
|
|
13348
|
+
size: live2 ? [Math.round(minS), Math.round(maxS)] : null
|
|
12527
13349
|
};
|
|
12528
13350
|
});
|
|
12529
13351
|
window.__particleToggle = (on, onlyIndex) => {
|
|
@@ -12689,8 +13511,11 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
12689
13511
|
try {
|
|
12690
13512
|
const fe = pkg.getEntry(parsedPkg, fp);
|
|
12691
13513
|
if (!fe) continue;
|
|
13514
|
+
const bytes = sanitizeFontForBrowser(
|
|
13515
|
+
fe instanceof Uint8Array ? fe : new Uint8Array(fe)
|
|
13516
|
+
);
|
|
12692
13517
|
const fam = "wefont_" + fp.split("/").pop().replace(/[^a-zA-Z0-9]/g, "_");
|
|
12693
|
-
const url = URL.createObjectURL(new Blob([
|
|
13518
|
+
const url = URL.createObjectURL(new Blob([bytes]));
|
|
12694
13519
|
const ff = new FontFace(fam, `url(${url})`);
|
|
12695
13520
|
await ff.load();
|
|
12696
13521
|
document.fonts.add(ff);
|
|
@@ -12734,7 +13559,7 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
12734
13559
|
// 与对象/效果开关/常量/general 统一走 engineTimers(P1-1)。
|
|
12735
13560
|
...timerOpts,
|
|
12736
13561
|
mediaControl,
|
|
12737
|
-
windowTitle:
|
|
13562
|
+
windowTitle: windowDriver.snapshot,
|
|
12738
13563
|
openUserShortcut: shortcuts.openUserShortcut,
|
|
12739
13564
|
getLayerText: (name) => textLayerText.get(name),
|
|
12740
13565
|
onError: (e) => {
|
|
@@ -12982,7 +13807,7 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
12982
13807
|
return clone;
|
|
12983
13808
|
},
|
|
12984
13809
|
mediaControl,
|
|
12985
|
-
windowTitle:
|
|
13810
|
+
windowTitle: windowDriver.snapshot,
|
|
12986
13811
|
openUserShortcut: shortcuts.openUserShortcut,
|
|
12987
13812
|
isScreensaver: false
|
|
12988
13813
|
};
|
|
@@ -13009,8 +13834,8 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
13009
13834
|
const ctrl = anim.createAnimation(def.animation);
|
|
13010
13835
|
ctrl.field = field;
|
|
13011
13836
|
ctrl.baseValue = def.value;
|
|
13012
|
-
const
|
|
13013
|
-
ctrl.baseNumeric = Array.isArray(
|
|
13837
|
+
const live2 = layer[field];
|
|
13838
|
+
ctrl.baseNumeric = Array.isArray(live2) ? live2.slice() : live2;
|
|
13014
13839
|
layer.animationList.push(ctrl);
|
|
13015
13840
|
if (ctrl.name) layer.animations[ctrl.name] = ctrl;
|
|
13016
13841
|
animRuns.push({ layer, field, ctrl });
|
|
@@ -13227,8 +14052,10 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
13227
14052
|
const t = (now - start - pauseAccum) / 1e3;
|
|
13228
14053
|
inputView.update(pointerSrc.state);
|
|
13229
14054
|
if (mediaSim.enabled) {
|
|
13230
|
-
|
|
13231
|
-
|
|
14055
|
+
if (live?.media) live.media.pump();
|
|
14056
|
+
else simMedia.update(t);
|
|
14057
|
+
const snap = mediaSnapshot();
|
|
14058
|
+
const evts = media.diffMediaEvents(lastMediaSnap, snap);
|
|
13232
14059
|
if (evts.length) {
|
|
13233
14060
|
for (const { name, event } of evts) {
|
|
13234
14061
|
for (const sb of mediaHooks) {
|
|
@@ -13236,10 +14063,11 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
13236
14063
|
sb.callMedia(name, event);
|
|
13237
14064
|
}
|
|
13238
14065
|
}
|
|
13239
|
-
lastMediaSnap = media.cloneMediaSnapshot(
|
|
14066
|
+
lastMediaSnap = media.cloneMediaSnapshot(snap);
|
|
13240
14067
|
}
|
|
13241
14068
|
}
|
|
13242
|
-
|
|
14069
|
+
if (live?.windowTitle) live.windowTitle.pump();
|
|
14070
|
+
else simWindow.update(t);
|
|
13243
14071
|
for (const run of animRuns) {
|
|
13244
14072
|
run.ctrl.advance(interval / 1e3);
|
|
13245
14073
|
const field = run.field;
|
|
@@ -13307,8 +14135,12 @@ vec3 DecompressNormal(vec4 tex) {
|
|
|
13307
14135
|
}
|
|
13308
14136
|
if (visibilityDirty) recomputeVisibility();
|
|
13309
14137
|
if (audioSim.enabled) {
|
|
13310
|
-
|
|
13311
|
-
|
|
14138
|
+
if (audioDriverRef.current) audioDriverRef.current.pump();
|
|
14139
|
+
else simAudio.update(t);
|
|
14140
|
+
fillAudioBuffers(
|
|
14141
|
+
audioViews,
|
|
14142
|
+
audioDriverRef.current ? audioDriverRef.current.snapshot : simAudio.snapshot
|
|
14143
|
+
);
|
|
13312
14144
|
}
|
|
13313
14145
|
if (attachFollows.length) {
|
|
13314
14146
|
mdl.followAttachments(attachFollows, t, getBoneOverrides);
|