fix: keep world splat loading responsive

This commit is contained in:
Your Name 2026-04-06 02:31:33 +08:00
parent 58b4fc49b9
commit 14009f869e

View File

@ -157,7 +157,7 @@ function lerpSparkRevealProfile(
}
const SPLAT_REVEAL_DURATION_MS = 2600;
const SPLAT_PARTICLE_SAMPLE_LIMIT = 5200;
const SPLAT_PARTICLE_SAMPLE_LIMIT = 1800;
const KEY_TO_MOVEMENT: Partial<Record<string, MovementCommand>> = {
KeyW: "forward",
@ -217,10 +217,12 @@ function buildSplatParticleCloudData(
splatMesh: SparkSplatMesh,
sampleLimit = SPLAT_PARTICLE_SAMPLE_LIMIT
): SplatParticleCloudData | null {
const totalSplats = Math.max(0, splatMesh.packedSplats?.numSplats || 0);
const packedSplats = splatMesh.packedSplats;
const totalSplats = Math.max(0, packedSplats?.numSplats || 0);
if (!totalSplats) return null;
const sampleStep = Math.max(1, Math.floor(totalSplats / sampleLimit));
const targetCount = Math.min(sampleLimit, totalSplats);
const sampleStep = Math.max(totalSplats / targetCount, 1);
const points: Array<{
center: THREE.Vector3;
color: THREE.Color;
@ -228,17 +230,17 @@ function buildSplatParticleCloudData(
}> = [];
const warmWhite = new THREE.Color("#fff1dc");
splatMesh.forEachSplat((index, center, _scales, _quaternion, opacity, color) => {
if (points.length >= sampleLimit) return;
if (index % sampleStep !== 0) return;
if (opacity < 0.035) return;
for (let sampleIndex = 0; sampleIndex < targetCount; sampleIndex += 1) {
const index = Math.min(Math.floor(sampleIndex * sampleStep), totalSplats - 1);
const { center, opacity, color } = packedSplats.getSplat(index);
if (opacity < 0.035) continue;
points.push({
center: center.clone(),
color: color.clone(),
center,
color,
opacity,
});
});
}
if (!points.length) return null;
@ -758,12 +760,13 @@ function WorldSplatScene({
applySparkRevealProfile(sparkRenderer, splatMesh, SPLAT_LOADING_PROFILE);
let cancelled = false;
let idleCallbackId: number | null = null;
let timeoutId: ReturnType<typeof globalThis.setTimeout> | null = null;
splatMesh.initialized
.then(() => {
if (cancelled) return;
window.requestAnimationFrame(() => {
if (cancelled || hasAppliedInitialViewRef.current) return;
setParticleCloudData(buildSplatParticleCloudData(splatMesh));
positionCameraInsideWorld({
camera,
controls: orbitControlsRef.current,
@ -772,6 +775,19 @@ function WorldSplatScene({
});
hasAppliedInitialViewRef.current = true;
onLoadStateChange?.("ready");
const buildParticleCloud = () => {
if (cancelled) return;
setParticleCloudData(buildSplatParticleCloudData(splatMesh));
};
if ("requestIdleCallback" in window) {
idleCallbackId = window.requestIdleCallback(buildParticleCloud, {
timeout: 900,
});
} else {
timeoutId = globalThis.setTimeout(buildParticleCloud, 120);
}
});
})
.catch(() => {
@ -779,6 +795,12 @@ function WorldSplatScene({
});
return () => {
cancelled = true;
if (idleCallbackId !== null && "cancelIdleCallback" in window) {
window.cancelIdleCallback(idleCallbackId);
}
if (timeoutId !== null) {
globalThis.clearTimeout(timeoutId);
}
splatMesh.dispose();
};
}, [camera, groundPlaneOffset, onLoadStateChange, sparkRenderer, splatMesh]);