diff --git a/frontend/src/components/Character3D.vue b/frontend/src/components/Character3D.vue index 6f72d23..089b96c 100755 --- a/frontend/src/components/Character3D.vue +++ b/frontend/src/components/Character3D.vue @@ -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(); }