Implement model optimization and caching for 3D characters

- Added a new modelsProxyRouter to handle requests for optimized 3D character models.
- Introduced modelsProxyService to manage GLB file optimization using gltf-transform with Draco compression.
- Updated app.js to include the new modelsProxyRouter for API access.
- Enhanced .gitignore to exclude model cache files.
- Added scripts for optimizing GLB models and updated README with optimization instructions.
- Integrated DRACOLoader in Character3D.vue for loading compressed models.
- Updated FamilyView.vue to streamline character rendering logic.
This commit is contained in:
Torsten Schulz (local)
2026-01-22 13:24:47 +01:00
parent 09af7af228
commit 4379b0b955
21 changed files with 4515 additions and 71 deletions

View File

@@ -6,6 +6,8 @@
import { markRaw } from 'vue';
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
import { getApiBaseURL } from '@/utils/axios.js';
export default {
name: 'Character3D',
@@ -76,12 +78,9 @@ export default {
modelPath() {
const gender = this.actualGender;
const ageGroup = this.ageGroup;
// Versuche zuerst altersspezifisches Modell
const specificModel = `/models/3d/falukant/characters/${gender}_${ageGroup}.glb`;
// Fallback auf Basis-Modell
return specificModel;
const base = getApiBaseURL();
const prefix = base ? `${base}/api/models/3d/falukant/characters` : '/api/models/3d/falukant/characters';
return `${prefix}/${gender}_${ageGroup}.glb`;
}
},
watch: {
@@ -150,8 +149,8 @@ export default {
},
loadBackground() {
// Zufällig einen Hintergrund auswählen
const backgrounds = ['bg1.png', 'bg2.png'];
// Optimierte Versionen (512×341, ~130 KB); Originale ~3 MB
const backgrounds = ['bg1_opt.png', 'bg2_opt.png'];
const randomBg = backgrounds[Math.floor(Math.random() * backgrounds.length)];
const bgPath = `/images/falukant/backgrounds/${randomBg}`;
@@ -200,18 +199,24 @@ export default {
}
try {
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('/draco/gltf/');
const loader = new GLTFLoader();
loader.setDRACOLoader(dracoLoader);
const modelPath = this.modelPath;
// Versuche zuerst spezifisches Modell, dann Fallback
const base = getApiBaseURL();
const prefix = base ? `${base}/api/models/3d/falukant/characters` : '/api/models/3d/falukant/characters';
const fallbackPath = `${prefix}/${this.actualGender}.glb`;
let gltf;
try {
gltf = await loader.loadAsync(modelPath);
} catch (error) {
console.warn(`Could not load ${modelPath}, trying fallback model`);
// Fallback auf Basis-Modell
const fallbackPath = `/models/3d/falukant/characters/${this.actualGender}.glb`;
gltf = await loader.loadAsync(fallbackPath);
} finally {
dracoLoader.dispose();
}
// Modell als raw markieren, um Vue's Reactivity zu vermeiden

View File

@@ -48,3 +48,4 @@ apiClient.interceptors.response.use(response => {
});
export default apiClient;
export { getApiBaseURL };

View File

@@ -2,14 +2,6 @@
<div class="contenthidden">
<StatusBar />
<div class="contentscroll family-layout">
<!-- 3D-Modell für man selbst links -->
<div class="self-character-3d" v-if="ownCharacter">
<Character3D
:gender="ownCharacter.gender"
:age="ownCharacter.age"
/>
</div>
<div class="family-content">
<h2>{{ $t('falukant.family.title') }}</h2>