chart-preview 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +7 -3
- package/CHANGELOG.md +12 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +84 -13
- package/dist/index.mjs +84 -13
- package/package.json +2 -2
- package/src/ChartPreview.ts +94 -13
- package/src/ChartPreviewPlayer.ts +12 -0
|
@@ -22,9 +22,14 @@ jobs:
|
|
|
22
22
|
- name: Setup Node.js
|
|
23
23
|
uses: actions/setup-node@v4
|
|
24
24
|
with:
|
|
25
|
-
node-version:
|
|
25
|
+
node-version: 24
|
|
26
26
|
cache: "npm"
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
# npm trusted publishing needs npm >= 11.5.1; a cached Node 24.x can bundle an older npm.
|
|
29
|
+
- name: Install npm for trusted publishing
|
|
30
|
+
run: |
|
|
31
|
+
npm install -g npm@^11.5.1
|
|
32
|
+
npm --version
|
|
28
33
|
|
|
29
34
|
- name: Install Dependencies
|
|
30
35
|
run: npm ci
|
|
@@ -35,4 +40,3 @@ jobs:
|
|
|
35
40
|
publish: npm run release
|
|
36
41
|
env:
|
|
37
42
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
38
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# chart-preview
|
|
2
2
|
|
|
3
|
+
## 1.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2175c5f: Draw five-fret open tap notes instead of skipping them. They use the open note texture with the tap marking of the other tap notes.
|
|
8
|
+
|
|
9
|
+
## 1.3.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- fe294b3: Add manual resize functionality to ChartPreview and trigger resize in ChartPreviewPlayer on fullscreen change
|
|
14
|
+
|
|
3
15
|
## 1.2.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -180,6 +180,11 @@ declare class ChartPreview {
|
|
|
180
180
|
get chartCurrentTimeMs(): number;
|
|
181
181
|
/** Total duration in milliseconds */
|
|
182
182
|
get chartEndTimeMs(): number;
|
|
183
|
+
/**
|
|
184
|
+
* Manually triggers a resize of the camera and renderer.
|
|
185
|
+
* Call this when the container size changes (e.g., entering/exiting fullscreen).
|
|
186
|
+
*/
|
|
187
|
+
resize(): void;
|
|
183
188
|
/**
|
|
184
189
|
* Cleans up all resources. Call this when discarding the preview.
|
|
185
190
|
* The preview should not be used after calling dispose().
|
package/dist/index.js
CHANGED
|
@@ -241,6 +241,44 @@ function createPlaceholderTexture() {
|
|
|
241
241
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
242
242
|
return texture;
|
|
243
243
|
}
|
|
244
|
+
function createTapTexture(strumTexture) {
|
|
245
|
+
const rimPx = 8;
|
|
246
|
+
const rimFadePx = 6;
|
|
247
|
+
const image = strumTexture.image;
|
|
248
|
+
const canvas = document.createElement("canvas");
|
|
249
|
+
canvas.width = image.width;
|
|
250
|
+
canvas.height = image.height;
|
|
251
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
252
|
+
ctx.drawImage(image, 0, 0);
|
|
253
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
254
|
+
const pixels = imageData.data;
|
|
255
|
+
const clamp01 = (value) => Math.min(Math.max(value, 0), 1);
|
|
256
|
+
const colorWeight = (i) => {
|
|
257
|
+
const max = Math.max(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
258
|
+
const min = Math.min(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
259
|
+
const saturation = max === 0 ? 0 : (max - min) / max;
|
|
260
|
+
return pixels[i + 3] === 0 ? 0 : clamp01((saturation - 0.2) / 0.3);
|
|
261
|
+
};
|
|
262
|
+
for (let y = 0; y < canvas.height; y++) {
|
|
263
|
+
let runStart = 0;
|
|
264
|
+
for (let x = 0; x <= canvas.width; x++) {
|
|
265
|
+
if (x < canvas.width && colorWeight((y * canvas.width + x) * 4) > 0) {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
for (let runX = runStart; runX < x; runX++) {
|
|
269
|
+
const distanceFromEnd = Math.min(runX - runStart, x - 1 - runX) + 1;
|
|
270
|
+
const rim = clamp01((rimPx + rimFadePx - distanceFromEnd) / rimFadePx);
|
|
271
|
+
const i = (y * canvas.width + runX) * 4;
|
|
272
|
+
pixels[i + 3] *= 1 - colorWeight(i) * (1 - rim);
|
|
273
|
+
}
|
|
274
|
+
runStart = x + 1;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
ctx.putImageData(imageData, 0, 0);
|
|
278
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
279
|
+
texture.colorSpace = strumTexture.colorSpace;
|
|
280
|
+
return texture;
|
|
281
|
+
}
|
|
244
282
|
var AnimatedTextureManager = class {
|
|
245
283
|
constructor() {
|
|
246
284
|
this.animatedTextures = [];
|
|
@@ -409,6 +447,17 @@ var _ChartPreview = class {
|
|
|
409
447
|
get chartEndTimeMs() {
|
|
410
448
|
return this.audioManager.chartEndTimeMs;
|
|
411
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Manually triggers a resize of the camera and renderer.
|
|
452
|
+
* Call this when the container size changes (e.g., entering/exiting fullscreen).
|
|
453
|
+
*/
|
|
454
|
+
resize() {
|
|
455
|
+
this.camera.triggerResize();
|
|
456
|
+
this.renderer.triggerResize();
|
|
457
|
+
if (this.paused) {
|
|
458
|
+
this.animateFrame(false);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
412
461
|
/**
|
|
413
462
|
* Cleans up all resources. Call this when discarding the preview.
|
|
414
463
|
* The preview should not be used after calling dispose().
|
|
@@ -496,6 +545,10 @@ var ChartCamera = class extends THREE.PerspectiveCamera {
|
|
|
496
545
|
this.aspect = width / height;
|
|
497
546
|
this.updateProjectionMatrix();
|
|
498
547
|
}
|
|
548
|
+
/** Manually trigger a resize update */
|
|
549
|
+
triggerResize() {
|
|
550
|
+
this.onResize();
|
|
551
|
+
}
|
|
499
552
|
dispose() {
|
|
500
553
|
window.removeEventListener("resize", this.resizeListener);
|
|
501
554
|
this.clear();
|
|
@@ -518,6 +571,10 @@ var ChartRenderer = class extends THREE.WebGLRenderer {
|
|
|
518
571
|
const height = this.divContainer.offsetHeight ?? window.innerHeight;
|
|
519
572
|
this.setSize(width, height);
|
|
520
573
|
}
|
|
574
|
+
/** Manually trigger a resize update */
|
|
575
|
+
triggerResize() {
|
|
576
|
+
this.onResize();
|
|
577
|
+
}
|
|
521
578
|
dispose() {
|
|
522
579
|
window.removeEventListener("resize", this.resizeListener);
|
|
523
580
|
super.dispose();
|
|
@@ -1102,26 +1159,33 @@ async function loadTextures(instrumentType, options = {}) {
|
|
|
1102
1159
|
[import_scan_chart.noteTypes.blue, "blue"],
|
|
1103
1160
|
[import_scan_chart.noteTypes.orange, "orange"]
|
|
1104
1161
|
]);
|
|
1105
|
-
const modifiers = /* @__PURE__ */ new Map([
|
|
1106
|
-
[import_scan_chart.noteFlags.strum, "-strum"],
|
|
1107
|
-
[import_scan_chart.noteFlags.hopo, "-hopo"],
|
|
1108
|
-
[import_scan_chart.noteFlags.tap, "-tap"]
|
|
1109
|
-
]);
|
|
1110
1162
|
const spFlags = /* @__PURE__ */ new Map([
|
|
1111
1163
|
[import_scan_chart.noteFlags.none, ""],
|
|
1112
1164
|
[SP_FLAG, "-sp"]
|
|
1113
1165
|
]);
|
|
1114
1166
|
for (const [laneKey, laneName] of lanes) {
|
|
1115
|
-
for (
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1167
|
+
for (const [spFlagKey, spFlagName] of spFlags) {
|
|
1168
|
+
const strumTexture = addTexture(
|
|
1169
|
+
laneKey,
|
|
1170
|
+
import_scan_chart.noteFlags.strum | spFlagKey,
|
|
1171
|
+
`5fret-${laneName}-strum${spFlagName}`
|
|
1172
|
+
);
|
|
1173
|
+
addTexture(
|
|
1174
|
+
laneKey,
|
|
1175
|
+
import_scan_chart.noteFlags.hopo | spFlagKey,
|
|
1176
|
+
`5fret-${laneName}-hopo${spFlagName}`
|
|
1177
|
+
);
|
|
1178
|
+
if (laneKey === import_scan_chart.noteTypes.open) {
|
|
1179
|
+
reuseTexture(
|
|
1180
|
+
laneKey,
|
|
1181
|
+
import_scan_chart.noteFlags.tap | spFlagKey,
|
|
1182
|
+
strumTexture.then(createTapTexture)
|
|
1183
|
+
);
|
|
1184
|
+
} else {
|
|
1121
1185
|
addTexture(
|
|
1122
1186
|
laneKey,
|
|
1123
|
-
|
|
1124
|
-
`5fret-${laneName}${
|
|
1187
|
+
import_scan_chart.noteFlags.tap | spFlagKey,
|
|
1188
|
+
`5fret-${laneName}-tap${spFlagName}`
|
|
1125
1189
|
);
|
|
1126
1190
|
}
|
|
1127
1191
|
}
|
|
@@ -1760,6 +1824,13 @@ var ChartPreviewPlayer = class extends HTMLElement {
|
|
|
1760
1824
|
this.container.classList.toggle("fullscreen", this._isFullscreen);
|
|
1761
1825
|
this.fullscreenBtn.innerHTML = this._isFullscreen ? ICONS.fullscreenExit : ICONS.fullscreen;
|
|
1762
1826
|
this.fullscreenBtn.title = this._isFullscreen ? "Exit fullscreen" : "Enter fullscreen";
|
|
1827
|
+
if (this.chartPreview) {
|
|
1828
|
+
requestAnimationFrame(() => {
|
|
1829
|
+
requestAnimationFrame(() => {
|
|
1830
|
+
this.chartPreview?.resize();
|
|
1831
|
+
});
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1763
1834
|
};
|
|
1764
1835
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
1765
1836
|
this.render();
|
package/dist/index.mjs
CHANGED
|
@@ -201,6 +201,44 @@ function createPlaceholderTexture() {
|
|
|
201
201
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
202
202
|
return texture;
|
|
203
203
|
}
|
|
204
|
+
function createTapTexture(strumTexture) {
|
|
205
|
+
const rimPx = 8;
|
|
206
|
+
const rimFadePx = 6;
|
|
207
|
+
const image = strumTexture.image;
|
|
208
|
+
const canvas = document.createElement("canvas");
|
|
209
|
+
canvas.width = image.width;
|
|
210
|
+
canvas.height = image.height;
|
|
211
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true });
|
|
212
|
+
ctx.drawImage(image, 0, 0);
|
|
213
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
214
|
+
const pixels = imageData.data;
|
|
215
|
+
const clamp01 = (value) => Math.min(Math.max(value, 0), 1);
|
|
216
|
+
const colorWeight = (i) => {
|
|
217
|
+
const max = Math.max(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
218
|
+
const min = Math.min(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
219
|
+
const saturation = max === 0 ? 0 : (max - min) / max;
|
|
220
|
+
return pixels[i + 3] === 0 ? 0 : clamp01((saturation - 0.2) / 0.3);
|
|
221
|
+
};
|
|
222
|
+
for (let y = 0; y < canvas.height; y++) {
|
|
223
|
+
let runStart = 0;
|
|
224
|
+
for (let x = 0; x <= canvas.width; x++) {
|
|
225
|
+
if (x < canvas.width && colorWeight((y * canvas.width + x) * 4) > 0) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
for (let runX = runStart; runX < x; runX++) {
|
|
229
|
+
const distanceFromEnd = Math.min(runX - runStart, x - 1 - runX) + 1;
|
|
230
|
+
const rim = clamp01((rimPx + rimFadePx - distanceFromEnd) / rimFadePx);
|
|
231
|
+
const i = (y * canvas.width + runX) * 4;
|
|
232
|
+
pixels[i + 3] *= 1 - colorWeight(i) * (1 - rim);
|
|
233
|
+
}
|
|
234
|
+
runStart = x + 1;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
ctx.putImageData(imageData, 0, 0);
|
|
238
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
239
|
+
texture.colorSpace = strumTexture.colorSpace;
|
|
240
|
+
return texture;
|
|
241
|
+
}
|
|
204
242
|
var AnimatedTextureManager = class {
|
|
205
243
|
constructor() {
|
|
206
244
|
this.animatedTextures = [];
|
|
@@ -369,6 +407,17 @@ var _ChartPreview = class {
|
|
|
369
407
|
get chartEndTimeMs() {
|
|
370
408
|
return this.audioManager.chartEndTimeMs;
|
|
371
409
|
}
|
|
410
|
+
/**
|
|
411
|
+
* Manually triggers a resize of the camera and renderer.
|
|
412
|
+
* Call this when the container size changes (e.g., entering/exiting fullscreen).
|
|
413
|
+
*/
|
|
414
|
+
resize() {
|
|
415
|
+
this.camera.triggerResize();
|
|
416
|
+
this.renderer.triggerResize();
|
|
417
|
+
if (this.paused) {
|
|
418
|
+
this.animateFrame(false);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
372
421
|
/**
|
|
373
422
|
* Cleans up all resources. Call this when discarding the preview.
|
|
374
423
|
* The preview should not be used after calling dispose().
|
|
@@ -456,6 +505,10 @@ var ChartCamera = class extends THREE.PerspectiveCamera {
|
|
|
456
505
|
this.aspect = width / height;
|
|
457
506
|
this.updateProjectionMatrix();
|
|
458
507
|
}
|
|
508
|
+
/** Manually trigger a resize update */
|
|
509
|
+
triggerResize() {
|
|
510
|
+
this.onResize();
|
|
511
|
+
}
|
|
459
512
|
dispose() {
|
|
460
513
|
window.removeEventListener("resize", this.resizeListener);
|
|
461
514
|
this.clear();
|
|
@@ -478,6 +531,10 @@ var ChartRenderer = class extends THREE.WebGLRenderer {
|
|
|
478
531
|
const height = this.divContainer.offsetHeight ?? window.innerHeight;
|
|
479
532
|
this.setSize(width, height);
|
|
480
533
|
}
|
|
534
|
+
/** Manually trigger a resize update */
|
|
535
|
+
triggerResize() {
|
|
536
|
+
this.onResize();
|
|
537
|
+
}
|
|
481
538
|
dispose() {
|
|
482
539
|
window.removeEventListener("resize", this.resizeListener);
|
|
483
540
|
super.dispose();
|
|
@@ -1062,26 +1119,33 @@ async function loadTextures(instrumentType, options = {}) {
|
|
|
1062
1119
|
[noteTypes.blue, "blue"],
|
|
1063
1120
|
[noteTypes.orange, "orange"]
|
|
1064
1121
|
]);
|
|
1065
|
-
const modifiers = /* @__PURE__ */ new Map([
|
|
1066
|
-
[noteFlags.strum, "-strum"],
|
|
1067
|
-
[noteFlags.hopo, "-hopo"],
|
|
1068
|
-
[noteFlags.tap, "-tap"]
|
|
1069
|
-
]);
|
|
1070
1122
|
const spFlags = /* @__PURE__ */ new Map([
|
|
1071
1123
|
[noteFlags.none, ""],
|
|
1072
1124
|
[SP_FLAG, "-sp"]
|
|
1073
1125
|
]);
|
|
1074
1126
|
for (const [laneKey, laneName] of lanes) {
|
|
1075
|
-
for (
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1127
|
+
for (const [spFlagKey, spFlagName] of spFlags) {
|
|
1128
|
+
const strumTexture = addTexture(
|
|
1129
|
+
laneKey,
|
|
1130
|
+
noteFlags.strum | spFlagKey,
|
|
1131
|
+
`5fret-${laneName}-strum${spFlagName}`
|
|
1132
|
+
);
|
|
1133
|
+
addTexture(
|
|
1134
|
+
laneKey,
|
|
1135
|
+
noteFlags.hopo | spFlagKey,
|
|
1136
|
+
`5fret-${laneName}-hopo${spFlagName}`
|
|
1137
|
+
);
|
|
1138
|
+
if (laneKey === noteTypes.open) {
|
|
1139
|
+
reuseTexture(
|
|
1140
|
+
laneKey,
|
|
1141
|
+
noteFlags.tap | spFlagKey,
|
|
1142
|
+
strumTexture.then(createTapTexture)
|
|
1143
|
+
);
|
|
1144
|
+
} else {
|
|
1081
1145
|
addTexture(
|
|
1082
1146
|
laneKey,
|
|
1083
|
-
|
|
1084
|
-
`5fret-${laneName}${
|
|
1147
|
+
noteFlags.tap | spFlagKey,
|
|
1148
|
+
`5fret-${laneName}-tap${spFlagName}`
|
|
1085
1149
|
);
|
|
1086
1150
|
}
|
|
1087
1151
|
}
|
|
@@ -1724,6 +1788,13 @@ var ChartPreviewPlayer = class extends HTMLElement {
|
|
|
1724
1788
|
this.container.classList.toggle("fullscreen", this._isFullscreen);
|
|
1725
1789
|
this.fullscreenBtn.innerHTML = this._isFullscreen ? ICONS.fullscreenExit : ICONS.fullscreen;
|
|
1726
1790
|
this.fullscreenBtn.title = this._isFullscreen ? "Exit fullscreen" : "Enter fullscreen";
|
|
1791
|
+
if (this.chartPreview) {
|
|
1792
|
+
requestAnimationFrame(() => {
|
|
1793
|
+
requestAnimationFrame(() => {
|
|
1794
|
+
this.chartPreview?.resize();
|
|
1795
|
+
});
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1727
1798
|
};
|
|
1728
1799
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
1729
1800
|
this.render();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chart-preview",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"author": "Geo",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"repository": {
|
|
28
28
|
"type": "git",
|
|
29
|
-
"url": "git+
|
|
29
|
+
"url": "git+https://github.com/Geomitron/chart-preview.git"
|
|
30
30
|
},
|
|
31
31
|
"bugs": {
|
|
32
32
|
"url": "https://github.com/Geomitron/chart-preview/issues"
|
package/src/ChartPreview.ts
CHANGED
|
@@ -259,6 +259,56 @@ function createPlaceholderTexture(): THREE.Texture {
|
|
|
259
259
|
return texture;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Marks `strumTexture` the way the five-fret tap textures mark tap notes:
|
|
264
|
+
* each row of colored pixels is hollowed out, except for a rim at both ends of
|
|
265
|
+
* the row. With these values, the result closely matches each five-fret tap
|
|
266
|
+
* texture when this is applied to the matching strum texture.
|
|
267
|
+
*/
|
|
268
|
+
function createTapTexture(strumTexture: THREE.Texture): THREE.Texture {
|
|
269
|
+
const rimPx = 8;
|
|
270
|
+
const rimFadePx = 6;
|
|
271
|
+
const image = strumTexture.image as HTMLCanvasElement | HTMLImageElement;
|
|
272
|
+
const canvas = document.createElement("canvas");
|
|
273
|
+
canvas.width = image.width;
|
|
274
|
+
canvas.height = image.height;
|
|
275
|
+
const ctx = canvas.getContext("2d", { willReadFrequently: true })!;
|
|
276
|
+
ctx.drawImage(image, 0, 0);
|
|
277
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
278
|
+
const pixels = imageData.data;
|
|
279
|
+
const clamp01 = (value: number) => Math.min(Math.max(value, 0), 1);
|
|
280
|
+
|
|
281
|
+
/** 0 for grey or transparent pixels, rising to 1 for strongly colored pixels. */
|
|
282
|
+
const colorWeight = (i: number) => {
|
|
283
|
+
const max = Math.max(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
284
|
+
const min = Math.min(pixels[i], pixels[i + 1], pixels[i + 2]);
|
|
285
|
+
const saturation = max === 0 ? 0 : (max - min) / max;
|
|
286
|
+
return pixels[i + 3] === 0 ? 0 : clamp01((saturation - 0.2) / 0.3);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
for (let y = 0; y < canvas.height; y++) {
|
|
290
|
+
let runStart = 0;
|
|
291
|
+
for (let x = 0; x <= canvas.width; x++) {
|
|
292
|
+
if (x < canvas.width && colorWeight((y * canvas.width + x) * 4) > 0) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
// Pixels runStart to x - 1 are a run of colored pixels
|
|
296
|
+
for (let runX = runStart; runX < x; runX++) {
|
|
297
|
+
const distanceFromEnd = Math.min(runX - runStart, x - 1 - runX) + 1;
|
|
298
|
+
const rim = clamp01((rimPx + rimFadePx - distanceFromEnd) / rimFadePx);
|
|
299
|
+
const i = (y * canvas.width + runX) * 4;
|
|
300
|
+
pixels[i + 3] *= 1 - colorWeight(i) * (1 - rim);
|
|
301
|
+
}
|
|
302
|
+
runStart = x + 1;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
ctx.putImageData(imageData, 0, 0);
|
|
307
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
308
|
+
texture.colorSpace = strumTexture.colorSpace;
|
|
309
|
+
return texture;
|
|
310
|
+
}
|
|
311
|
+
|
|
262
312
|
/** Collection of animated textures that need to be ticked each frame */
|
|
263
313
|
class AnimatedTextureManager {
|
|
264
314
|
private animatedTextures: AnimatedTexture[] = [];
|
|
@@ -509,6 +559,19 @@ export class ChartPreview {
|
|
|
509
559
|
return this.audioManager.chartEndTimeMs;
|
|
510
560
|
}
|
|
511
561
|
|
|
562
|
+
/**
|
|
563
|
+
* Manually triggers a resize of the camera and renderer.
|
|
564
|
+
* Call this when the container size changes (e.g., entering/exiting fullscreen).
|
|
565
|
+
*/
|
|
566
|
+
resize() {
|
|
567
|
+
this.camera.triggerResize();
|
|
568
|
+
this.renderer.triggerResize();
|
|
569
|
+
// Re-render a frame to immediately show the updated size
|
|
570
|
+
if (this.paused) {
|
|
571
|
+
this.animateFrame(false);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
512
575
|
/**
|
|
513
576
|
* Cleans up all resources. Call this when discarding the preview.
|
|
514
577
|
* The preview should not be used after calling dispose().
|
|
@@ -618,6 +681,11 @@ class ChartCamera extends THREE.PerspectiveCamera {
|
|
|
618
681
|
this.updateProjectionMatrix();
|
|
619
682
|
}
|
|
620
683
|
|
|
684
|
+
/** Manually trigger a resize update */
|
|
685
|
+
triggerResize() {
|
|
686
|
+
this.onResize();
|
|
687
|
+
}
|
|
688
|
+
|
|
621
689
|
dispose() {
|
|
622
690
|
window.removeEventListener("resize", this.resizeListener);
|
|
623
691
|
this.clear();
|
|
@@ -642,6 +710,11 @@ class ChartRenderer extends THREE.WebGLRenderer {
|
|
|
642
710
|
this.setSize(width, height);
|
|
643
711
|
}
|
|
644
712
|
|
|
713
|
+
/** Manually trigger a resize update */
|
|
714
|
+
triggerResize() {
|
|
715
|
+
this.onResize();
|
|
716
|
+
}
|
|
717
|
+
|
|
645
718
|
override dispose() {
|
|
646
719
|
window.removeEventListener("resize", this.resizeListener);
|
|
647
720
|
super.dispose();
|
|
@@ -1459,27 +1532,35 @@ async function loadTextures(
|
|
|
1459
1532
|
[noteTypes.blue, "blue"],
|
|
1460
1533
|
[noteTypes.orange, "orange"],
|
|
1461
1534
|
]);
|
|
1462
|
-
const modifiers = new Map([
|
|
1463
|
-
[noteFlags.strum, "-strum"],
|
|
1464
|
-
[noteFlags.hopo, "-hopo"],
|
|
1465
|
-
[noteFlags.tap, "-tap"],
|
|
1466
|
-
]);
|
|
1467
1535
|
const spFlags = new Map([
|
|
1468
1536
|
[noteFlags.none, ""],
|
|
1469
1537
|
[SP_FLAG, "-sp"],
|
|
1470
1538
|
]);
|
|
1471
1539
|
|
|
1472
1540
|
for (const [laneKey, laneName] of lanes) {
|
|
1473
|
-
for (
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1541
|
+
for (const [spFlagKey, spFlagName] of spFlags) {
|
|
1542
|
+
const strumTexture = addTexture(
|
|
1543
|
+
laneKey,
|
|
1544
|
+
noteFlags.strum | spFlagKey,
|
|
1545
|
+
`5fret-${laneName}-strum${spFlagName}`
|
|
1546
|
+
);
|
|
1547
|
+
addTexture(
|
|
1548
|
+
laneKey,
|
|
1549
|
+
noteFlags.hopo | spFlagKey,
|
|
1550
|
+
`5fret-${laneName}-hopo${spFlagName}`
|
|
1551
|
+
);
|
|
1552
|
+
if (laneKey === noteTypes.open) {
|
|
1553
|
+
// There is no open tap texture
|
|
1554
|
+
reuseTexture(
|
|
1555
|
+
laneKey,
|
|
1556
|
+
noteFlags.tap | spFlagKey,
|
|
1557
|
+
strumTexture.then(createTapTexture)
|
|
1558
|
+
);
|
|
1559
|
+
} else {
|
|
1479
1560
|
addTexture(
|
|
1480
1561
|
laneKey,
|
|
1481
|
-
|
|
1482
|
-
`5fret-${laneName}${
|
|
1562
|
+
noteFlags.tap | spFlagKey,
|
|
1563
|
+
`5fret-${laneName}-tap${spFlagName}`
|
|
1483
1564
|
);
|
|
1484
1565
|
}
|
|
1485
1566
|
}
|
|
@@ -549,6 +549,18 @@ export class ChartPreviewPlayer extends HTMLElement {
|
|
|
549
549
|
this.fullscreenBtn.title = this._isFullscreen
|
|
550
550
|
? "Exit fullscreen"
|
|
551
551
|
: "Enter fullscreen";
|
|
552
|
+
|
|
553
|
+
// Trigger resize on the chart preview after a short delay
|
|
554
|
+
// to ensure the container dimensions have updated
|
|
555
|
+
if (this.chartPreview) {
|
|
556
|
+
// Use requestAnimationFrame to ensure DOM has updated
|
|
557
|
+
requestAnimationFrame(() => {
|
|
558
|
+
// Double RAF to ensure layout has been recalculated
|
|
559
|
+
requestAnimationFrame(() => {
|
|
560
|
+
this.chartPreview?.resize();
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
}
|
|
552
564
|
};
|
|
553
565
|
|
|
554
566
|
private onSeekStart() {
|