Preload default textures for instant texture picker opening

Fetch and parse .tex files in the background when a textured part
loads, and pass the results to TexturePickerModal as a prop. The
modal no longer fetches on mount, eliminating the loading delay.
This commit is contained in:
Christian Semmler 2026-02-07 13:52:46 -08:00
parent c3083b02af
commit 8adeb9fed6
2 changed files with 41 additions and 46 deletions

View File

@ -1,18 +1,17 @@
<script>
import { onMount } from 'svelte';
import { parseTex } from '../../core/formats/TexParser.js';
import { quantizeImage, squareTexture } from '../../core/savegame/imageQuantizer.js';
import { saveCustomTexture, listCustomTextures, deleteCustomTexture } from '../../core/savegame/textureStorage.js';
import Carousel from '../Carousel.svelte';
export let textureInfo;
export let palette = null;
export let defaults = null;
export let onSelect = () => {};
export let onClose = () => {};
let defaults = [];
let defaultTextures = [];
let customTextures = [];
let loadingDefaults = true;
let fileInput;
let activeTab = 'default';
let selectedCustomId = null;
@ -22,40 +21,20 @@
let targetHeight = 128;
onMount(async () => {
await loadDefaults();
initDefaults();
await loadCustomTextures();
});
async function loadDefaults() {
loadingDefaults = true;
const loaded = [];
for (const texFile of textureInfo.texFiles) {
try {
const response = await fetch(`/${texFile}.tex`);
if (!response.ok) continue;
const buffer = await response.arrayBuffer();
const parsed = parseTex(buffer);
if (parsed.textures.length > 0) {
const tex = parsed.textures[0];
loaded.push({
name: texFile,
...tex,
dataUrl: textureToDataUrl(tex)
});
}
} catch (e) {
console.error(`Failed to load ${texFile}.tex:`, e);
}
function initDefaults() {
const source = defaults || [];
defaultTextures = source.map(tex => ({
...tex,
dataUrl: textureToDataUrl(tex)
}));
if (defaultTextures.length > 0) {
targetWidth = defaultTextures[0].width;
targetHeight = defaultTextures[0].height;
}
if (loaded.length > 0) {
targetWidth = loaded[0].width;
targetHeight = loaded[0].height;
}
defaults = loaded;
loadingDefaults = false;
}
async function loadCustomTextures() {
@ -127,7 +106,7 @@
// Use the WDB palette (passed as prop) — this is the palette the game's
// DirectDraw surface actually uses. Fall back to first default's palette.
const targetPalette = palette || defaults[0]?.palette;
const targetPalette = palette || defaultTextures[0]?.palette;
if (!targetPalette) return;
const img = new Image();
@ -210,11 +189,8 @@
<div class="modal-body">
{#if activeTab === 'default'}
{#if loadingDefaults}
<div class="loading">Loading textures...</div>
{:else}
<div class="texture-grid">
{#each defaults as tex}
{#each defaultTextures as tex}
<button
type="button"
class="texture-thumb"
@ -225,7 +201,6 @@
</button>
{/each}
</div>
{/if}
{:else}
{#if customTextures.length > 0}
<div class="custom-carousel">
@ -333,13 +308,6 @@
padding: 14px;
}
.loading {
color: var(--color-text-muted);
font-size: 0.85em;
text-align: center;
padding: 20px 0;
}
.texture-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);

View File

@ -12,6 +12,7 @@
Act1PlaneIndices
} from '../../core/savegame/constants.js';
import { squareTexture } from '../../core/savegame/imageQuantizer.js';
import { parseTex } from '../../core/formats/TexParser.js';
import NavButton from '../NavButton.svelte';
import ResetButton from '../ResetButton.svelte';
import EditorTooltip from '../EditorTooltip.svelte';
@ -45,6 +46,7 @@
let showTextureModal = false;
let texturePalette = null;
let wdbTexture = null;
let preloadedDefaults = null;
// Current part info from flat list
$: currentEntry = allParts[globalIndex];
@ -207,12 +209,36 @@
// Load part with current color, textures, and parts map for shared LOD lookup
renderer.loadPartWithColor(partRoi, currentColorValue, textures, partsMap)
loadedPartKey = partKey;
// Preload default .tex files in background for the texture picker
if (textureInfo) {
preloadDefaultTextures(textureInfo);
} else {
preloadedDefaults = null;
}
} catch (e) {
console.error('Failed to load part:', e);
partError = e.message;
}
}
async function preloadDefaultTextures(info) {
const loaded = [];
for (const texFile of info.texFiles) {
const response = await fetch(`/${texFile}.tex`);
if (!response.ok) continue;
const buffer = await response.arrayBuffer();
const parsed = parseTex(buffer);
if (parsed.textures.length > 0) {
loaded.push({ name: texFile, ...parsed.textures[0] });
}
}
// Only apply if textureInfo hasn't changed since we started
if (textureInfo === info) {
preloadedDefaults = loaded;
}
}
function prevPart() {
globalIndex = globalIndex > 0 ? globalIndex - 1 : allParts.length - 1;
loadedPartKey = null;
@ -358,6 +384,7 @@
<TexturePickerModal
{textureInfo}
palette={texturePalette}
defaults={preloadedDefaults}
onSelect={handleTextureSelect}
onClose={() => showTextureModal = false}
/>