fix(falukant): use nearest lower age model fallback
Some checks failed
Deploy to production / deploy (push) Has been cancelled

This commit is contained in:
Torsten Schulz (local)
2026-09-30 13:56:21 +02:00
parent afff062251
commit 3647b2c61f

View File

@@ -27,15 +27,12 @@ import { getApiBaseURL } from '@/utils/axios.js';
/** Backend-Route: GET /api/models/3d/falukant/characters/:filename (Proxy mit Draco-Optimierung) */ /** Backend-Route: GET /api/models/3d/falukant/characters/:filename (Proxy mit Draco-Optimierung) */
const MODELS_API_PATH = '/api/models/3d/falukant/characters'; const MODELS_API_PATH = '/api/models/3d/falukant/characters';
const OPTIMIZED_MODEL_SUFFIX = '_opt.glb'; const OPTIMIZED_MODEL_SUFFIX = '_opt.glb';
// Optional: zusätzliche Namenskonventionen, die vor dem Standardpfad ausprobiert werden. // Only concrete age models are used. If an exact age is not available, choose
// Hier werden mögliche Alternativ-Dateinamen aufgeführt, die für bestimmte Altersstufen // the nearest lower one instead of the generic gender/age-group fallback.
// (z.B. weibliche Modelle für 1-4 Jahre) existieren könnten. Trage hier die tatsächlichen const AVAILABLE_MODEL_AGES = Object.freeze({
// Dateinamen ein, falls dein Backend solche Varianten enthält. female: Object.freeze([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 25, 30, 40, 50, 60]),
const AGE_ALTERNATIVE_SUFFIXES = [ male: Object.freeze([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 25, 30, 40, 50, 60]),
'_alt.glb', });
'_special.glb',
'_v2.glb'
];
// Per-age camera offsets applied after auto-fit. Values are additive (meters). // Per-age camera offsets applied after auto-fit. Values are additive (meters).
// Set `y` to raise/lower the camera, `z` to move it closer/further. // Set `y` to raise/lower the camera, `z` to move it closer/further.
@@ -196,18 +193,6 @@ export default {
if (age <= 17) return 'teen'; if (age <= 17) return 'teen';
return 'adult'; return 'adult';
}, },
modelPath() {
const base = getApiBaseURL();
const prefix = base ? `${base}${MODELS_API_PATH}` : MODELS_API_PATH;
return `${prefix}/${this.actualGender}_${this.ageGroup}.glb`;
},
exactAgeModelPath() {
// Pfad für genaues Alter (z.B. female_1y.glb für Alter 1)
const age = this.actualAge;
const base = getApiBaseURL();
const prefix = base ? `${base}${MODELS_API_PATH}` : MODELS_API_PATH;
return `${prefix}/${this.actualGender}_${age}y.glb`;
},
fallbackImageSrc() { fallbackImageSrc() {
return this.actualGender === 'female' return this.actualGender === 'female'
? '/images/mascot/mascot_female.png' ? '/images/mascot/mascot_female.png'
@@ -219,7 +204,7 @@ export default {
if (this.is3DDisabled) return; if (this.is3DDisabled) return;
await this.loadModel(); await this.loadModel();
}, },
async ageGroup() { async actualAge() {
if (this.is3DDisabled) return; if (this.is3DDisabled) return;
await this.loadModel(); await this.loadModel();
} }
@@ -454,35 +439,14 @@ export default {
const base = getApiBaseURL(); const base = getApiBaseURL();
const prefix = base ? `${base}${MODELS_API_PATH}` : MODELS_API_PATH; const prefix = base ? `${base}${MODELS_API_PATH}` : MODELS_API_PATH;
// Fallback-Hierarchie:
// Standard:
// 1. Exaktes Altersmodell
// 2. Altersbereich
// 3. Basis-Modell
// Lightweight:
// 1. Altersbereich
// 2. Basis-Modell
const exactAgePath = this.toOptimizedModelPath(this.exactAgeModelPath);
const ageGroupPath = this.toOptimizedModelPath(this.modelPath);
const fallbackPath = `${prefix}/${this.actualGender}${OPTIMIZED_MODEL_SUFFIX}`;
// Build a list of candidate paths. For every age and gender we first try
// possible alternate filenames for the exact-age model (e.g. female_1y_alt.glb),
// then the exact-age file, then the age-group file, and finally the base gender
// fallback. This makes the age-specific model the primary source and the
// age-group only a later fallback.
const candidates = [];
const modelAge = this.actualAge; const modelAge = this.actualAge;
const availableAges = AVAILABLE_MODEL_AGES[this.actualGender] || [];
// Try exact-age variants with configured suffixes first (works for any gender/age) const candidateAges = availableAges
for (const sfx of AGE_ALTERNATIVE_SUFFIXES) { .filter((age) => age <= modelAge)
candidates.push(this.toOptimizedModelPath(`${prefix}/${this.actualGender}_${modelAge}y${sfx}`)); .sort((left, right) => right - left);
} const candidates = candidateAges.map((age) =>
`${prefix}/${this.actualGender}_${age}y${OPTIMIZED_MODEL_SUFFIX}`
// Standard preference order: exact age, age group, fallback );
candidates.push(exactAgePath);
candidates.push(ageGroupPath);
candidates.push(fallbackPath);
let gltf; let gltf;
try { try {
@@ -505,10 +469,8 @@ export default {
// store for debug overlay // store for debug overlay
this.debugInfo.modelPath = chosenPath; this.debugInfo.modelPath = chosenPath;
// determine modelType // determine modelType
if (chosenPath === exactAgePath) this.debugInfo.modelType = 'exactAge'; const chosenAge = candidateAges[candidates.indexOf(chosenPath)];
else if (chosenPath === ageGroupPath) this.debugInfo.modelType = 'ageGroup'; this.debugInfo.modelType = chosenAge === modelAge ? 'exactAge' : 'nearestLowerAge';
else if (chosenPath === fallbackPath) this.debugInfo.modelType = 'fallback';
else this.debugInfo.modelType = 'alternate';
} finally { } finally {
dracoLoader.dispose(); dracoLoader.dispose();
} }