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) */
const MODELS_API_PATH = '/api/models/3d/falukant/characters';
const OPTIMIZED_MODEL_SUFFIX = '_opt.glb';
// Optional: zusätzliche Namenskonventionen, die vor dem Standardpfad ausprobiert werden.
// Hier werden mögliche Alternativ-Dateinamen aufgeführt, die für bestimmte Altersstufen
// (z.B. weibliche Modelle für 1-4 Jahre) existieren könnten. Trage hier die tatsächlichen
// Dateinamen ein, falls dein Backend solche Varianten enthält.
const AGE_ALTERNATIVE_SUFFIXES = [
'_alt.glb',
'_special.glb',
'_v2.glb'
];
// Only concrete age models are used. If an exact age is not available, choose
// the nearest lower one instead of the generic gender/age-group fallback.
const AVAILABLE_MODEL_AGES = Object.freeze({
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]),
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]),
});
// 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.
@@ -196,18 +193,6 @@ export default {
if (age <= 17) return 'teen';
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() {
return this.actualGender === 'female'
? '/images/mascot/mascot_female.png'
@@ -219,7 +204,7 @@ export default {
if (this.is3DDisabled) return;
await this.loadModel();
},
async ageGroup() {
async actualAge() {
if (this.is3DDisabled) return;
await this.loadModel();
}
@@ -454,35 +439,14 @@ export default {
const base = getApiBaseURL();
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;
// Try exact-age variants with configured suffixes first (works for any gender/age)
for (const sfx of AGE_ALTERNATIVE_SUFFIXES) {
candidates.push(this.toOptimizedModelPath(`${prefix}/${this.actualGender}_${modelAge}y${sfx}`));
}
// Standard preference order: exact age, age group, fallback
candidates.push(exactAgePath);
candidates.push(ageGroupPath);
candidates.push(fallbackPath);
const availableAges = AVAILABLE_MODEL_AGES[this.actualGender] || [];
const candidateAges = availableAges
.filter((age) => age <= modelAge)
.sort((left, right) => right - left);
const candidates = candidateAges.map((age) =>
`${prefix}/${this.actualGender}_${age}y${OPTIMIZED_MODEL_SUFFIX}`
);
let gltf;
try {
@@ -505,10 +469,8 @@ export default {
// store for debug overlay
this.debugInfo.modelPath = chosenPath;
// determine modelType
if (chosenPath === exactAgePath) this.debugInfo.modelType = 'exactAge';
else if (chosenPath === ageGroupPath) this.debugInfo.modelType = 'ageGroup';
else if (chosenPath === fallbackPath) this.debugInfo.modelType = 'fallback';
else this.debugInfo.modelType = 'alternate';
const chosenAge = candidateAges[candidates.indexOf(chosenPath)];
this.debugInfo.modelType = chosenAge === modelAge ? 'exactAge' : 'nearestLowerAge';
} finally {
dracoLoader.dispose();
}