add 3d to srebot (#1350)

This commit is contained in:
NotSoToothless
2026-06-21 07:54:35 -07:00
committed by GitHub
parent 28a635438d
commit 8a084fb644
9 changed files with 37226 additions and 6 deletions
+57 -4
View File
@@ -53,6 +53,9 @@ class ReplayCanvas {
this._teamNames = (data.teamNames && typeof data.teamNames === 'object') ? data.teamNames : {};
this._winnerSlot = Number(data.winnerSlot) || 0; // winning team slot (1/2)
this._mode = 'ground';
this._viewMode = '2d';
this.view3d = null;
this.supports3d = false;
const hasAircraft = data.entities.some(e => e.type === 'aircraft');
this.hasAirMode = !!(this._airCoords && this._fullMapLevel && hasAircraft);
@@ -166,6 +169,9 @@ class ReplayCanvas {
async init() {
this._buildDOM();
await Promise.all([this._loadMap(), this._loadEntityIcons()]);
this._initView3d();
this._onResize = () => { if (this._viewMode === '3d') this.view3d?.resize(); };
window.addEventListener('resize', this._onResize);
this.playing = true;
this.playBtn.innerHTML = '<i class="fas fa-pause"></i>';
this.lastFrameTime = performance.now();
@@ -173,6 +179,42 @@ class ReplayCanvas {
this.animFrameId = requestAnimationFrame(this._tick);
}
_initView3d() {
try {
if (typeof window.ReplayCanvas3D !== 'function') return;
this.view3d = new window.ReplayCanvas3D(this.view3dContainer, this.data);
this.supports3d = true;
} catch (e) {
this.view3d = null;
this.supports3d = false;
}
}
_renderActive() {
if (this._viewMode === '3d') { this.view3d?.setTime(this.currentTime); }
else { this.render(); }
}
setViewMode(mode) {
const next = mode === '3d' && this.view3d ? '3d' : '2d';
if (next === this._viewMode) return;
this._viewMode = next;
const is3d = next === '3d';
this.canvas.classList.toggle('rc-hidden', is3d);
this.view3dContainer.classList.toggle('rc-hidden', !is3d);
if (is3d) {
this.view3d.setMode(this._mode);
this.view3d.resize();
this.view3d.setTime(this.currentTime);
} else {
this.render();
}
}
focus(playerId) {
if (this._viewMode === '3d') this.view3d?.focus(playerId);
}
_buildDOM() {
this.container.innerHTML = '';
const layout = document.createElement('div');
@@ -190,12 +232,18 @@ class ReplayCanvas {
// Tickets meter above the battle view (its top aligns with the team panels)
this._buildTicketsBar(center);
const stage = document.createElement('div');
stage.className = 'rc-stage';
this.canvas = document.createElement('canvas');
this.canvas.width = this.canvasSize;
this.canvas.height = this.canvasSize;
this.canvas.className = 'rc-canvas';
this.ctx = this.canvas.getContext('2d');
center.appendChild(this.canvas);
stage.appendChild(this.canvas);
this.view3dContainer = document.createElement('div');
this.view3dContainer.className = 'rc-3d-container rc-hidden';
stage.appendChild(this.view3dContainer);
center.appendChild(stage);
// Controls
const controls = document.createElement('div');
@@ -243,7 +291,7 @@ class ReplayCanvas {
this.currentTime = this.tStart + (this.scrubber.value / 1000) * (this.tEnd - this.tStart);
this._updatePanelDeathStates();
this._updateBattleLog();
this.render();
this._renderActive();
this._updateTicketsBar(this.currentTime);
});
controls.querySelectorAll('.rc-sp').forEach(btn => {
@@ -331,6 +379,7 @@ class ReplayCanvas {
}
});
row.addEventListener('mouseleave', () => this._setHighlight(null));
row.addEventListener('click', () => this.focus(parseInt(row.dataset.playerId)));
});
}
@@ -873,8 +922,9 @@ class ReplayCanvas {
this._drawMapToCanvas();
// Recompute death positions in new coordinate space
this._computeDeaths();
this.view3d?.setMode(mode);
// Render immediately
this.render();
this._renderActive();
}
_togglePlay() {
@@ -897,7 +947,7 @@ class ReplayCanvas {
this.playBtn.innerHTML = '<i class="fas fa-play"></i>';
}
}
this.render();
this._renderActive();
this._updateTicketsBar(this.currentTime);
this._updateControls();
// Update panel death states + battle log every ~250ms
@@ -1145,6 +1195,9 @@ class ReplayCanvas {
destroy() {
if (this.animFrameId) { cancelAnimationFrame(this.animFrameId); this.animFrameId = null; }
if (this._onResize) window.removeEventListener('resize', this._onResize);
this.view3d?.dispose();
this.view3d = null;
this.container.innerHTML = '';
}
}