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

View File

@ -12,6 +12,7 @@
Act1PlaneIndices Act1PlaneIndices
} from '../../core/savegame/constants.js'; } from '../../core/savegame/constants.js';
import { squareTexture } from '../../core/savegame/imageQuantizer.js'; import { squareTexture } from '../../core/savegame/imageQuantizer.js';
import { parseTex } from '../../core/formats/TexParser.js';
import NavButton from '../NavButton.svelte'; import NavButton from '../NavButton.svelte';
import ResetButton from '../ResetButton.svelte'; import ResetButton from '../ResetButton.svelte';
import EditorTooltip from '../EditorTooltip.svelte'; import EditorTooltip from '../EditorTooltip.svelte';
@ -45,6 +46,7 @@
let showTextureModal = false; let showTextureModal = false;
let texturePalette = null; let texturePalette = null;
let wdbTexture = null; let wdbTexture = null;
let preloadedDefaults = null;
// Current part info from flat list // Current part info from flat list
$: currentEntry = allParts[globalIndex]; $: currentEntry = allParts[globalIndex];
@ -207,12 +209,36 @@
// Load part with current color, textures, and parts map for shared LOD lookup // Load part with current color, textures, and parts map for shared LOD lookup
renderer.loadPartWithColor(partRoi, currentColorValue, textures, partsMap) renderer.loadPartWithColor(partRoi, currentColorValue, textures, partsMap)
loadedPartKey = partKey; loadedPartKey = partKey;
// Preload default .tex files in background for the texture picker
if (textureInfo) {
preloadDefaultTextures(textureInfo);
} else {
preloadedDefaults = null;
}
} catch (e) { } catch (e) {
console.error('Failed to load part:', e); console.error('Failed to load part:', e);
partError = e.message; 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() { function prevPart() {
globalIndex = globalIndex > 0 ? globalIndex - 1 : allParts.length - 1; globalIndex = globalIndex > 0 ? globalIndex - 1 : allParts.length - 1;
loadedPartKey = null; loadedPartKey = null;
@ -358,6 +384,7 @@
<TexturePickerModal <TexturePickerModal
{textureInfo} {textureInfo}
palette={texturePalette} palette={texturePalette}
defaults={preloadedDefaults}
onSelect={handleTextureSelect} onSelect={handleTextureSelect}
onClose={() => showTextureModal = false} onClose={() => showTextureModal = false}
/> />