1
This commit is contained in:
@@ -6,7 +6,10 @@ import type {
|
||||
} from '../../../packages/shared/src/contracts/match3dRuntime';
|
||||
import type { Match3DGeneratedItemAsset } from '../../../packages/shared/src/contracts/match3dWorks';
|
||||
import { isDebugMode } from '../../config/debugMode';
|
||||
import { readAssetBytes } from '../../services/assetReadUrlService';
|
||||
import {
|
||||
readMatch3DGeneratedModelBytes,
|
||||
resolveMatch3DGeneratedModelAssetSource,
|
||||
} from '../../services/match3dGeneratedModelCache';
|
||||
import {
|
||||
isItemState,
|
||||
resolveRenderableItemFrame,
|
||||
@@ -111,6 +114,8 @@ type PhysicsRuntime = {
|
||||
animationId: number | null;
|
||||
camera: ThreeCamera;
|
||||
entries: Map<string, PhysicsEntry>;
|
||||
failedGeneratedModelTypeIds: Set<string>;
|
||||
generatedModelByType: Map<string, Match3DGeneratedItemAsset>;
|
||||
generatedModelTemplates: Match3DGeneratedModelTemplateMap;
|
||||
pendingSpawns: Map<string, PendingPhysicsSpawn>;
|
||||
raycaster: import('three').Raycaster;
|
||||
@@ -170,7 +175,7 @@ export const MATCH3D_EXTRUDED_READABLE_SHAPES: ReadonlySet<Match3DGeometryShape>
|
||||
function normalizeMatch3DGeneratedModelSource(
|
||||
asset: Match3DGeneratedItemAsset,
|
||||
) {
|
||||
return asset.modelSrc?.trim() || asset.modelObjectKey?.trim() || '';
|
||||
return resolveMatch3DGeneratedModelAssetSource(asset);
|
||||
}
|
||||
|
||||
function compareMatch3DGeneratedTypeId(left: string, right: string) {
|
||||
@@ -213,6 +218,7 @@ export function buildMatch3DGeneratedAssetTypeMap(
|
||||
...resolved.asset,
|
||||
modelSrc: resolved.source,
|
||||
});
|
||||
debugMatch3DGeneratedModelMapped(itemTypeId, resolved.source);
|
||||
});
|
||||
|
||||
return assetMap;
|
||||
@@ -237,12 +243,16 @@ function resolveGeneratedModelSourceForItemType(
|
||||
return asset ? normalizeMatch3DGeneratedModelSource(asset) : '';
|
||||
}
|
||||
|
||||
function shouldLogMatch3DGeneratedModelDiagnostics() {
|
||||
return isDebugMode() && import.meta.env.MODE !== 'test';
|
||||
}
|
||||
|
||||
function warnMatch3DGeneratedModelLoadFailure(
|
||||
itemTypeId: string,
|
||||
source: string,
|
||||
error: unknown,
|
||||
) {
|
||||
if (!isDebugMode()) {
|
||||
if (!shouldLogMatch3DGeneratedModelDiagnostics()) {
|
||||
return;
|
||||
}
|
||||
const message =
|
||||
@@ -254,6 +264,32 @@ function warnMatch3DGeneratedModelLoadFailure(
|
||||
});
|
||||
}
|
||||
|
||||
function debugMatch3DGeneratedModelLoaded(
|
||||
itemTypeId: string,
|
||||
source: string,
|
||||
) {
|
||||
if (!shouldLogMatch3DGeneratedModelDiagnostics()) {
|
||||
return;
|
||||
}
|
||||
console.debug('[match3d] generated model loaded', {
|
||||
itemTypeId,
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
function debugMatch3DGeneratedModelMapped(
|
||||
itemTypeId: string,
|
||||
source: string,
|
||||
) {
|
||||
if (!shouldLogMatch3DGeneratedModelDiagnostics()) {
|
||||
return;
|
||||
}
|
||||
console.debug('[match3d] generated model mapped', {
|
||||
itemTypeId,
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
async function loadMatch3DGeneratedModelTemplate(
|
||||
templateMap: Match3DGeneratedModelTemplateMap,
|
||||
three: ThreeModule,
|
||||
@@ -265,11 +301,10 @@ async function loadMatch3DGeneratedModelTemplate(
|
||||
if (cached?.source === source) {
|
||||
return cached.scene;
|
||||
}
|
||||
const response = await readAssetBytes(source, {
|
||||
const bytes = await readMatch3DGeneratedModelBytes(source, {
|
||||
expireSeconds: 300,
|
||||
signal,
|
||||
});
|
||||
const bytes = await response.arrayBuffer();
|
||||
if (bytes.byteLength === 0) {
|
||||
throw new Error('抓大鹅 3D 模型内容为空');
|
||||
}
|
||||
@@ -297,6 +332,7 @@ async function loadMatch3DGeneratedModelTemplate(
|
||||
scene,
|
||||
source,
|
||||
});
|
||||
debugMatch3DGeneratedModelLoaded(itemTypeId, source);
|
||||
return scene;
|
||||
}
|
||||
|
||||
@@ -327,7 +363,6 @@ function createGeneratedModelMesh(
|
||||
}
|
||||
const position = toWorldPosition(item);
|
||||
const model = cloneThreeObjectWithMaterials(template);
|
||||
markObjectForItem(model, item.itemInstanceId);
|
||||
const bounds = new three.Box3().setFromObject(model);
|
||||
const size = bounds.getSize(new three.Vector3());
|
||||
const dimension = Math.max(size.x, size.y, size.z, 0.001);
|
||||
@@ -341,10 +376,13 @@ function createGeneratedModelMesh(
|
||||
model.position.sub(center);
|
||||
const bottomY = scaledBounds.min.y - center.y;
|
||||
model.position.y -= bottomY;
|
||||
const pivot = new three.Group();
|
||||
pivot.add(model);
|
||||
markObjectForItem(pivot, item.itemInstanceId);
|
||||
|
||||
return {
|
||||
lockReadableTop: false,
|
||||
mesh: model,
|
||||
mesh: pivot,
|
||||
radius: position.radius,
|
||||
shape: 'brick' as Match3DGeometryShape,
|
||||
topRotationY: ((item.layer % 12) / 12) * Math.PI * 2,
|
||||
@@ -1157,6 +1195,22 @@ function createItemMesh(
|
||||
);
|
||||
}
|
||||
|
||||
function shouldWaitForGeneratedModelTemplate(
|
||||
generatedModelByType: Map<string, Match3DGeneratedItemAsset>,
|
||||
templateMap: Match3DGeneratedModelTemplateMap,
|
||||
failedTypeIds: ReadonlySet<string>,
|
||||
itemTypeId: string,
|
||||
) {
|
||||
const source = resolveGeneratedModelSourceForItemType(
|
||||
generatedModelByType,
|
||||
itemTypeId,
|
||||
);
|
||||
// 中文注释:坏 GLB 或过期链接不能让整局空等模板;失败类型应立即走默认几何降级。
|
||||
return Boolean(
|
||||
source && !templateMap.has(itemTypeId) && !failedTypeIds.has(itemTypeId),
|
||||
);
|
||||
}
|
||||
|
||||
export function buildMatch3DPhysicsEntrySignature(
|
||||
runId: string,
|
||||
item: Match3DItemSnapshot,
|
||||
@@ -1192,6 +1246,16 @@ function createPhysicsEntryFromPendingSpawn(
|
||||
now: number,
|
||||
templateMap?: Match3DGeneratedModelTemplateMap | null,
|
||||
) {
|
||||
if (
|
||||
shouldWaitForGeneratedModelTemplate(
|
||||
runtime.generatedModelByType,
|
||||
runtime.generatedModelTemplates,
|
||||
runtime.failedGeneratedModelTypeIds,
|
||||
pendingSpawn.item.itemTypeId,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const visual = createItemMesh(runtime.three, pendingSpawn.item, templateMap);
|
||||
const asset = resolveGeometryAsset(pendingSpawn.item.visualKey);
|
||||
const colliderBounds = resolveMatch3DColliderBounds(asset, visual.radius);
|
||||
@@ -1273,7 +1337,17 @@ function createPhysicsEntryFromPendingSpawn(
|
||||
|
||||
function flushPendingPhysicsSpawns(runtime: PhysicsRuntime, now: number) {
|
||||
const readySpawns = [...runtime.pendingSpawns.entries()]
|
||||
.filter(([, pendingSpawn]) => now >= pendingSpawn.spawnAtMs)
|
||||
.filter(([, pendingSpawn]) => {
|
||||
if (now < pendingSpawn.spawnAtMs) {
|
||||
return false;
|
||||
}
|
||||
return !shouldWaitForGeneratedModelTemplate(
|
||||
runtime.generatedModelByType,
|
||||
runtime.generatedModelTemplates,
|
||||
runtime.failedGeneratedModelTypeIds,
|
||||
pendingSpawn.item.itemTypeId,
|
||||
);
|
||||
})
|
||||
.sort((left, right) => {
|
||||
if (left[1].spawnAtMs !== right[1].spawnAtMs) {
|
||||
return left[1].spawnAtMs - right[1].spawnAtMs;
|
||||
@@ -1317,6 +1391,7 @@ type TrayPreviewRuntime = {
|
||||
animationId: number | null;
|
||||
camera: ThreeCamera;
|
||||
entries: Map<string, ThreeObject3D>;
|
||||
failedGeneratedModelTypeIds: Set<string>;
|
||||
generatedModelTemplates: Match3DGeneratedModelTemplateMap;
|
||||
renderer: ThreeRenderer;
|
||||
scene: ThreeScene;
|
||||
@@ -1620,6 +1695,8 @@ export function Match3DTrayPreviewBoard({
|
||||
animationId: null,
|
||||
camera,
|
||||
entries: runtimeRef.current?.entries ?? new Map(),
|
||||
failedGeneratedModelTypeIds:
|
||||
runtimeRef.current?.failedGeneratedModelTypeIds ?? new Set(),
|
||||
generatedModelTemplates:
|
||||
runtimeRef.current?.generatedModelTemplates ?? new Map(),
|
||||
renderer,
|
||||
@@ -1645,6 +1722,7 @@ export function Match3DTrayPreviewBoard({
|
||||
animationId: window.requestAnimationFrame(animate),
|
||||
camera,
|
||||
entries: new Map(),
|
||||
failedGeneratedModelTypeIds: new Set(),
|
||||
generatedModelTemplates: new Map(),
|
||||
renderer,
|
||||
scene,
|
||||
@@ -1687,6 +1765,7 @@ export function Match3DTrayPreviewBoard({
|
||||
staleItemTypeIds.delete(itemTypeId);
|
||||
const hadFreshTemplate =
|
||||
runtime.generatedModelTemplates.get(itemTypeId)?.source === source;
|
||||
runtime.failedGeneratedModelTypeIds.delete(itemTypeId);
|
||||
void loadMatch3DGeneratedModelTemplate(
|
||||
runtime.generatedModelTemplates,
|
||||
runtime.three,
|
||||
@@ -1721,6 +1800,8 @@ export function Match3DTrayPreviewBoard({
|
||||
caughtError,
|
||||
);
|
||||
runtime.generatedModelTemplates.delete(itemTypeId);
|
||||
runtime.failedGeneratedModelTypeIds.add(itemTypeId);
|
||||
setTrayModelRevision((current) => current + 1);
|
||||
});
|
||||
});
|
||||
staleItemTypeIds.forEach((itemTypeId) => {
|
||||
@@ -1729,6 +1810,7 @@ export function Match3DTrayPreviewBoard({
|
||||
disposeThreeObject(template.scene);
|
||||
}
|
||||
runtime.generatedModelTemplates.delete(itemTypeId);
|
||||
runtime.failedGeneratedModelTypeIds.delete(itemTypeId);
|
||||
});
|
||||
|
||||
return () => {
|
||||
@@ -1785,7 +1867,9 @@ export function Match3DTrayPreviewBoard({
|
||||
const preview = createItemMesh(
|
||||
runtime.three,
|
||||
item,
|
||||
runtime.generatedModelTemplates,
|
||||
runtime.failedGeneratedModelTypeIds.has(item.itemTypeId)
|
||||
? null
|
||||
: runtime.generatedModelTemplates,
|
||||
);
|
||||
const model = preview.mesh;
|
||||
const rotation = resolveMatch3DTrayPreviewRotation(item.visualKey);
|
||||
@@ -2050,6 +2134,8 @@ export function Match3DPhysicsBoard({
|
||||
animationId: null,
|
||||
camera,
|
||||
entries: new Map(),
|
||||
failedGeneratedModelTypeIds: new Set(),
|
||||
generatedModelByType,
|
||||
generatedModelTemplates: new Map(),
|
||||
pendingSpawns: new Map(),
|
||||
raycaster: new three.Raycaster(),
|
||||
@@ -2157,6 +2243,7 @@ export function Match3DPhysicsBoard({
|
||||
if (!runtime) {
|
||||
return undefined;
|
||||
}
|
||||
runtime.generatedModelByType = generatedModelByType;
|
||||
const abortController = new AbortController();
|
||||
const staleItemTypeIds = new Set(runtime.generatedModelTemplates.keys());
|
||||
generatedModelByType.forEach((asset, itemTypeId) => {
|
||||
@@ -2167,6 +2254,7 @@ export function Match3DPhysicsBoard({
|
||||
}
|
||||
const hadFreshTemplate =
|
||||
runtime.generatedModelTemplates.get(itemTypeId)?.source === source;
|
||||
runtime.failedGeneratedModelTypeIds.delete(itemTypeId);
|
||||
void loadMatch3DGeneratedModelTemplate(
|
||||
runtime.generatedModelTemplates,
|
||||
runtime.three,
|
||||
@@ -2201,6 +2289,8 @@ export function Match3DPhysicsBoard({
|
||||
caughtError,
|
||||
);
|
||||
runtime.generatedModelTemplates.delete(itemTypeId);
|
||||
runtime.failedGeneratedModelTypeIds.add(itemTypeId);
|
||||
setGeneratedModelRevision((current) => current + 1);
|
||||
});
|
||||
});
|
||||
staleItemTypeIds.forEach((itemTypeId) => {
|
||||
@@ -2209,6 +2299,7 @@ export function Match3DPhysicsBoard({
|
||||
disposeThreeObject(template.scene);
|
||||
}
|
||||
runtime.generatedModelTemplates.delete(itemTypeId);
|
||||
runtime.failedGeneratedModelTypeIds.delete(itemTypeId);
|
||||
});
|
||||
|
||||
return () => {
|
||||
|
||||
Reference in New Issue
Block a user