feat(falukant): add interactive 3d controls
All checks were successful
Deploy to production / deploy (push) Successful in 1m57s
All checks were successful
Deploy to production / deploy (push) Successful in 1m57s
This commit is contained in:
@@ -134,6 +134,7 @@ export default {
|
||||
threeRuntime: null,
|
||||
threeLoaders: null,
|
||||
threeModelRuntime: null
|
||||
,controls: null
|
||||
,resizeObserver: null
|
||||
,intersectionObserver: null
|
||||
,isVisible: false
|
||||
@@ -320,6 +321,16 @@ export default {
|
||||
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
||||
container.appendChild(this.renderer.domElement);
|
||||
|
||||
const { OrbitControls } = await import('@/utils/threeControls.js');
|
||||
this.controls = markRaw(new OrbitControls(this.camera, this.renderer.domElement));
|
||||
this.controls.target.set(0, 1, 0);
|
||||
this.controls.enablePan = false;
|
||||
this.controls.enableDamping = true;
|
||||
this.controls.dampingFactor = 0.08;
|
||||
this.controls.minDistance = 1;
|
||||
this.controls.maxDistance = 8;
|
||||
this.controls.update();
|
||||
|
||||
// Verbesserte Beleuchtung für kräftigere Farben
|
||||
// HemisphereLight für etwas Umgebungsfarbe und natürliche Ground-Reflektion
|
||||
const hemi = new runtime.HemisphereLight(0xffffff, 0x888877, 0.6);
|
||||
@@ -556,7 +567,14 @@ export default {
|
||||
}
|
||||
|
||||
this.camera.position.set(0, camY, camZ);
|
||||
this.camera.lookAt(scaledCenter.x, scaledCenter.y, scaledCenter.z);
|
||||
if (this.controls) {
|
||||
this.controls.target.set(scaledCenter.x, scaledCenter.y, scaledCenter.z);
|
||||
this.controls.minDistance = Math.max(0.8, distance * 0.55);
|
||||
this.controls.maxDistance = Math.max(this.controls.minDistance + 1, distance * 3.2);
|
||||
this.controls.update();
|
||||
} else {
|
||||
this.camera.lookAt(scaledCenter.x, scaledCenter.y, scaledCenter.z);
|
||||
}
|
||||
this.onWindowResize();
|
||||
// debug info
|
||||
this.debugInfo.camera = { x: this.camera.position.x, y: this.camera.position.y, z: this.camera.position.z };
|
||||
@@ -600,6 +618,8 @@ export default {
|
||||
this.mixer.update(delta);
|
||||
}
|
||||
|
||||
this.controls?.update();
|
||||
|
||||
// Keine Bewegung - Modelle bleiben statisch
|
||||
|
||||
if (this.renderer && this.scene && this.camera) {
|
||||
@@ -657,6 +677,9 @@ export default {
|
||||
this.renderer = null;
|
||||
}
|
||||
|
||||
this.controls?.dispose();
|
||||
this.controls = null;
|
||||
|
||||
if (this.scene) {
|
||||
this.scene.clear();
|
||||
}
|
||||
@@ -693,6 +716,15 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.character-3d-container :deep(canvas) {
|
||||
cursor: grab;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.character-3d-container :deep(canvas):active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.character-fallback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -25,6 +25,8 @@ export default {
|
||||
camera: null,
|
||||
renderer: null,
|
||||
renderedModel: null,
|
||||
controls: null,
|
||||
onControlsChange: null,
|
||||
resizeObserver: null,
|
||||
visibilityObserver: null,
|
||||
isVisible: false,
|
||||
@@ -71,7 +73,10 @@ export default {
|
||||
const container = this.$refs.container;
|
||||
if (!container || import.meta.env.VITE_DISABLE_3D === 'true') return;
|
||||
|
||||
const runtime = await import('@/utils/threeRuntime.js');
|
||||
const [runtime, controlsRuntime] = await Promise.all([
|
||||
import('@/utils/threeRuntime.js'),
|
||||
import('@/utils/threeControls.js'),
|
||||
]);
|
||||
if (!this.isVisible || !this.$refs.container || this.renderer) return;
|
||||
this.scene = markRaw(new runtime.Scene());
|
||||
this.camera = markRaw(new runtime.PerspectiveCamera(36, 1, 0.1, 100));
|
||||
@@ -79,6 +84,15 @@ export default {
|
||||
this.renderer.setClearColor(0x000000, 0);
|
||||
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
|
||||
container.appendChild(this.renderer.domElement);
|
||||
this.camera.position.set(0, 1.5, 4.1);
|
||||
this.controls = markRaw(new controlsRuntime.OrbitControls(this.camera, this.renderer.domElement));
|
||||
this.controls.target.set(0, 1.05, 0);
|
||||
this.controls.enablePan = false;
|
||||
this.controls.minDistance = 2.2;
|
||||
this.controls.maxDistance = 8;
|
||||
this.onControlsChange = () => this.render();
|
||||
this.controls.addEventListener('change', this.onControlsChange);
|
||||
this.controls.update();
|
||||
|
||||
this.scene.add(new runtime.HemisphereLight(0xfff8e8, 0x665b50, 2.2));
|
||||
const keyLight = new runtime.DirectionalLight(0xffffff, 2.8);
|
||||
@@ -127,6 +141,7 @@ export default {
|
||||
const center = scaledBox.getCenter(new modelRuntime.Vector3());
|
||||
this.renderedModel.position.set(-center.x, -scaledBox.min.y, -center.z);
|
||||
this.scene.add(this.renderedModel);
|
||||
this.controls?.update();
|
||||
this.render();
|
||||
} catch (error) {
|
||||
console.error(`Unable to load 3D house model '${this.model}'`, error);
|
||||
@@ -139,8 +154,6 @@ export default {
|
||||
const width = Math.max(container.clientWidth, 1);
|
||||
const height = Math.max(container.clientHeight, 1);
|
||||
this.camera.aspect = width / height;
|
||||
this.camera.position.set(0, 1.5, 4.1);
|
||||
this.camera.lookAt(0, 1.05, 0);
|
||||
this.camera.updateProjectionMatrix();
|
||||
this.renderer.setSize(width, height, false);
|
||||
this.renderer.render(this.scene, this.camera);
|
||||
@@ -165,6 +178,12 @@ export default {
|
||||
this.resizeObserver?.disconnect();
|
||||
this.resizeObserver = null;
|
||||
this.removeModel();
|
||||
if (this.controls) {
|
||||
this.controls.removeEventListener('change', this.onControlsChange);
|
||||
this.controls.dispose();
|
||||
this.controls = null;
|
||||
this.onControlsChange = null;
|
||||
}
|
||||
if (this.renderer) {
|
||||
this.$refs.container?.removeChild(this.renderer.domElement);
|
||||
// Release immediately instead of waiting for garbage collection.
|
||||
@@ -208,4 +227,13 @@ export default {
|
||||
font-size: 0.82rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.house-3d :deep(canvas) {
|
||||
cursor: grab;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.house-3d :deep(canvas):active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
</style>
|
||||
|
||||
3
frontend/src/utils/threeControls.js
Normal file
3
frontend/src/utils/threeControls.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
|
||||
export { OrbitControls };
|
||||
Reference in New Issue
Block a user