mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-18 14:32:43 +00:00
feat(highway_3d): gem rims flash string-coloured, wire-fashion
On a confirmed hit the gem's outline now flashes in the STRING'S OWN colour with the same intensity treatment as the fret wires — the FRET_WIRE_HIT_INTENSITY emissive ramp, faded by the provider's alpha — instead of the fixed spring-green mHitBright rim. Just the rims: the lateral face fill keeps its existing green, and the sustain trail is untouched. Mechanics mirror the wires' pattern. mRimFlash[s] is one material per string (created with the other per-string materials, palette-retint aware, fog-exempt, disposed in teardown); drawNote() assigns it as the outline on a good verdict and records the verdict alpha into a per-frame per-string max (_rimFlashIn); the flash pass applies the intensity ramp once per string. Shared-per-string is the same compromise mGlow already makes — two same-string gems flashing in different phases share the brighter alpha. No decay tail of our own, deliberately: the material is only assigned while the provider confirms the note, and the provider's alpha already fades. When it goes silent the outline reverts, so idle intensity never shows. Signed-off-by: topkoa <topkoa@gmail.com>
This commit is contained in:
+3
-1
@@ -55,7 +55,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
survives only as the fallback on anchor-less charts). At most **two wires are ever lit at
|
survives only as the fallback on anchor-less charts). At most **two wires are ever lit at
|
||||||
once**: when overlapping decay tails (fast passages) would light a run of wires, the
|
once**: when overlapping decay tails (fast passages) would light a run of wires, the
|
||||||
flash collapses to the outermost pair of the lit span — one bracket, never a picket
|
flash collapses to the outermost pair of the lit span — one bracket, never a picket
|
||||||
fence. With no scorer attached, nothing changes.
|
fence. The gem's rim joins in: on a confirmed hit the outline flashes in the
|
||||||
|
string's own colour with the same intensity treatment as the wires, fading with the
|
||||||
|
scorer's alpha. With no scorer attached, nothing changes.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- **`GET /api/song/{f}?stems=1`** (new, opt-in) — returns the pack's playable stem
|
- **`GET /api/song/{f}?stems=1`** (new, opt-in) — returns the pack's playable stem
|
||||||
|
|||||||
@@ -4036,6 +4036,13 @@
|
|||||||
// Built in initScene() after mGlow. Array share the same material
|
// Built in initScene() after mGlow. Array share the same material
|
||||||
// instances so outline and face fill always match exactly.
|
// instances so outline and face fill always match exactly.
|
||||||
let mHitBright = [], mHitBrightArrays = [];
|
let mHitBright = [], mHitBrightArrays = [];
|
||||||
|
// Gem-rim hit flash ("just the rims"): per-string materials that flash
|
||||||
|
// in the STRING'S OWN colour with the same intensity treatment as the
|
||||||
|
// fret wires (FRET_WIRE_HIT_INTENSITY ramp, provider-alpha fade). Shared
|
||||||
|
// per string, so the applied intensity is the per-frame MAX alpha across
|
||||||
|
// that string's flashing gems — same compromise mGlow already makes.
|
||||||
|
let mRimFlash = [];
|
||||||
|
const _rimFlashIn = new Float32Array(S_COL.length);
|
||||||
// [verdict glow] Per-frame accumulation of the note-state provider's
|
// [verdict glow] Per-frame accumulation of the note-state provider's
|
||||||
// alpha (note_detect drives this from the live input level for held
|
// alpha (note_detect drives this from the live input level for held
|
||||||
// sustains, and as a time-fade for fresh strikes). Applied at the top of
|
// sustains, and as a time-fade for fresh strikes). Applied at the top of
|
||||||
@@ -7048,13 +7055,21 @@
|
|||||||
transparent: true, opacity: 1.0, depthWrite: false,
|
transparent: true, opacity: 1.0, depthWrite: false,
|
||||||
}));
|
}));
|
||||||
mHitBrightArrays = mHitBright.map(m => [m, m, m, m, mEdgeTransparent, mEdgeTransparent]);
|
mHitBrightArrays = mHitBright.map(m => [m, m, m, m, mEdgeTransparent, mEdgeTransparent]);
|
||||||
|
|
||||||
|
// Rim flash: string-coloured, wire-fashion intensity. Colour and
|
||||||
|
// emissive both take the palette colour so the rim reads as the
|
||||||
|
// string lighting up, not as a white wash over it.
|
||||||
|
mRimFlash = activePalette.map((c) => new T.MeshLambertMaterial({
|
||||||
|
color: c, emissive: c, emissiveIntensity: 1,
|
||||||
|
transparent: true, opacity: 1.0, depthWrite: false,
|
||||||
|
}));
|
||||||
// Readability (#2 / charrette): the note gems + their outlines punch THROUGH
|
// Readability (#2 / charrette): the note gems + their outlines punch THROUGH
|
||||||
// the distance fog so upcoming notes stay legible as they render in at the
|
// the distance fog so upcoming notes stay legible as they render in at the
|
||||||
// horizon. The board, lane, sustains and background scenery keep their
|
// horizon. The board, lane, sustains and background scenery keep their
|
||||||
// atmospheric fog — only the note-defining materials are exempted, so the
|
// atmospheric fog — only the note-defining materials are exempted, so the
|
||||||
// highway still reads as deep while the notes never dissolve into the haze.
|
// highway still reads as deep while the notes never dissolve into the haze.
|
||||||
[mWhiteOutline, mMissOutline].forEach(m => { if (m) m.fog = false; });
|
[mWhiteOutline, mMissOutline].forEach(m => { if (m) m.fog = false; });
|
||||||
[mStr, mGlow, mStrHitOutline, mHitBright].forEach(arr => arr && arr.forEach(m => { if (m) m.fog = false; }));
|
[mStr, mGlow, mStrHitOutline, mHitBright, mRimFlash].forEach(arr => arr && arr.forEach(m => { if (m) m.fog = false; }));
|
||||||
// Outline materials render at a lower renderOrder than the body.
|
// Outline materials render at a lower renderOrder than the body.
|
||||||
// The body is rendered on top with opacity:1 on hit/miss, which
|
// The body is rendered on top with opacity:1 on hit/miss, which
|
||||||
// fully covers the outline center — only the fringe that extends
|
// fully covers the outline center — only the fringe that extends
|
||||||
@@ -8365,6 +8380,10 @@
|
|||||||
if (mStr[s].emissive) mStr[s].emissive.setHex(c);
|
if (mStr[s].emissive) mStr[s].emissive.setHex(c);
|
||||||
}
|
}
|
||||||
if (mGlow[s]) mGlow[s].emissive.setHex(c);
|
if (mGlow[s]) mGlow[s].emissive.setHex(c);
|
||||||
|
if (mRimFlash[s]) {
|
||||||
|
mRimFlash[s].color.setHex(c);
|
||||||
|
mRimFlash[s].emissive.setHex(c);
|
||||||
|
}
|
||||||
if (mSus[s]) mSus[s].color.setHex(c);
|
if (mSus[s]) mSus[s].color.setHex(c);
|
||||||
if (mStrHitOutline[s]) {
|
if (mStrHitOutline[s]) {
|
||||||
mStrHitOutline[s].color.setHex(c);
|
mStrHitOutline[s].color.setHex(c);
|
||||||
@@ -10738,6 +10757,7 @@
|
|||||||
_scrStringAnticipation.fill(0, 0, nStr);
|
_scrStringAnticipation.fill(0, 0, nStr);
|
||||||
_scrFretHeat.fill(0); // always NFRETS+1, cheap flat fill
|
_scrFretHeat.fill(0); // always NFRETS+1, cheap flat fill
|
||||||
_fwHitIn.fill(0); // this frame's confirmed-hit frets
|
_fwHitIn.fill(0); // this frame's confirmed-hit frets
|
||||||
|
_rimFlashIn.fill(0); // this frame's per-string rim-flash alphas
|
||||||
_fwChordAcc.clear();
|
_fwChordAcc.clear();
|
||||||
_scrStrGlow.fill(0.5, 0, nStr);
|
_scrStrGlow.fill(0.5, 0, nStr);
|
||||||
_scrAccentFillBoost.fill(0, 0, nStr);
|
_scrAccentFillBoost.fill(0, 0, nStr);
|
||||||
@@ -12714,6 +12734,16 @@
|
|||||||
_m.emissiveIntensity = 1 + (FRET_WIRE_HIT_INTENSITY - 1) * _g;
|
_m.emissiveIntensity = 1 + (FRET_WIRE_HIT_INTENSITY - 1) * _g;
|
||||||
_m.opacity += (FRET_WIRE_HIT_OP - _m.opacity) * _g;
|
_m.opacity += (FRET_WIRE_HIT_OP - _m.opacity) * _g;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gem-rim flash: same intensity ramp as the wires, in the
|
||||||
|
// string's own colour. No decay tail of our own — the material
|
||||||
|
// is only ever ASSIGNED while the provider confirms the note,
|
||||||
|
// and the provider's alpha already fades; when it goes silent
|
||||||
|
// the outline reverts and idle intensity is irrelevant.
|
||||||
|
for (let _s = 0; _s < mRimFlash.length; _s++) {
|
||||||
|
const _m = mRimFlash[_s];
|
||||||
|
if (_m) _m.emissiveIntensity = 1 + (FRET_WIRE_HIT_INTENSITY - 1) * _rimFlashIn[_s];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Dynamic highway lane ──────────────────────────────────────
|
// ── Dynamic highway lane ──────────────────────────────────────
|
||||||
@@ -14028,8 +14058,11 @@
|
|||||||
_streakHits = 0; // #7 break the streak (heat eases down)
|
_streakHits = 0; // #7 break the streak (heat eases down)
|
||||||
if (_verdictMarks) _ndLabels.push({ x, y: y + NH * 1.7, z: noteZ + 0.02, labels: [{ text: '✗', color: '#ff5a7a' }] }); // #6
|
if (_verdictMarks) _ndLabels.push({ x, y: y + NH * 1.7, z: noteZ + 0.02, labels: [{ text: '✗', color: '#ff5a7a' }] }); // #6
|
||||||
} else if (_ndGood) {
|
} else if (_ndGood) {
|
||||||
_ndOutline = mHitBright[s] ?? mGlow[s];
|
// Rim flashes in the string's own colour, wire-fashion
|
||||||
|
// (intensity applied per string in the flash pass).
|
||||||
|
_ndOutline = mRimFlash[s] ?? mHitBright[s] ?? mGlow[s];
|
||||||
_ndFaceMat = mHitBrightArrays[s] ?? null;
|
_ndFaceMat = mHitBrightArrays[s] ?? null;
|
||||||
|
if (_vAlpha > _rimFlashIn[s]) _rimFlashIn[s] = _vAlpha;
|
||||||
_hitPunch = 1 + 0.22 * _hitFx * _vAlpha; // #3 scale-punch (biggest at strike, eases)
|
_hitPunch = 1 + 0.22 * _hitFx * _vAlpha; // #3 scale-punch (biggest at strike, eases)
|
||||||
if (_verdictMarks) { const _tc = _timingHex(_ndMatchedMark && _ndMatchedMark.timingState); _ndLabels.push({ x, y: y + NH * 1.7, z: noteZ + 0.02, labels: [{ text: '✓', color: '#' + _tc.toString(16).padStart(6, '0') }] }); } // #6 + #5
|
if (_verdictMarks) { const _tc = _timingHex(_ndMatchedMark && _ndMatchedMark.timingState); _ndLabels.push({ x, y: y + NH * 1.7, z: noteZ + 0.02, labels: [{ text: '✓', color: '#' + _tc.toString(16).padStart(6, '0') }] }); } // #6 + #5
|
||||||
if (_sparks && _hitFx > 0 && _vAlpha > 0.5) {
|
if (_sparks && _hitFx > 0 && _vAlpha > 0.5) {
|
||||||
@@ -15378,6 +15411,7 @@
|
|||||||
mHitSusOutline?.dispose?.();
|
mHitSusOutline?.dispose?.();
|
||||||
mEdgeTransparent?.dispose?.(); mEdgeTransparent = null;
|
mEdgeTransparent?.dispose?.(); mEdgeTransparent = null;
|
||||||
for (const m of mHitBright) m?.dispose?.(); mHitBright = []; mHitBrightArrays = [];
|
for (const m of mHitBright) m?.dispose?.(); mHitBright = []; mHitBrightArrays = [];
|
||||||
|
for (const m of mRimFlash) m?.dispose?.(); mRimFlash = [];
|
||||||
for (const k in txtCache) {
|
for (const k in txtCache) {
|
||||||
const tm = txtCache[k];
|
const tm = txtCache[k];
|
||||||
tm.userData.h3dGhostFretMeshMat?.dispose?.();
|
tm.userData.h3dGhostFretMeshMat?.dispose?.();
|
||||||
|
|||||||
Reference in New Issue
Block a user