Hello,
I am currently developing an Angular application and I use ThreeJS to load a model that I created in Blender and exported as a .glb file in it (wild mix, I know…). In my Angular application the model is in a component thats 30% of the viewport width:
app-bot {
height: 100%;
width: 30%;
}
My problem is that the model is not its true width and gets compressed horizontally. This is how it should look (Screenshot from an online glTF viewer, so the model itself seems fine):
The amount of compression actually depends on the viewport width, meaning that if I resize my browser window and refresh it changes:
This is my implementation:
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/Addons.js';
@Component({
selector: 'app-bot',
imports: [],
templateUrl: './bot.component.html',
styleUrl: './bot.component.scss',
})
export class BotComponent implements AfterViewInit {
// HTML Element: <canvas #rendererCanvas></canvas>
@ViewChild('rendererCanvas') rendererCanvas!: ElementRef;
private renderer!: THREE.WebGLRenderer;
private scene!: THREE.Scene;
private camera!: THREE.PerspectiveCamera;
private mixer: THREE.AnimationMixer | null = null;
private animationAction: THREE.AnimationAction | null = null;
ngAfterViewInit() {
this.initThree();
this.loadModel();
}
private initThree() {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera();
// Also already tried
// window.innerWidth / window.innerHeight,
// and
// width / height of parent container
// as camera aspect ratio
this.camera.position.set(0, 2.5, 5);
this.renderer = new THREE.WebGLRenderer({
canvas: this.rendererCanvas.nativeElement,
antialias: true,
alpha: true, // Enable alpha (transparency)
});
this.renderer.setClearColor(0x000000, 0); // Set clear color to transparent
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
// I also already tried out this:
// const width = this.rendererCanvas.nativeElement.clientWidth;
// const height = this.rendererCanvas.nativeElement.clientHeight;
// const aspectRatio = width / height; // Correct aspect ratio
// this.renderer.setSize(width, height);
// this.renderer.setPixelRatio(aspectRatio);
// Add light to the scene
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
this.scene.add(light);
// Add ambient light
const ambientLight = new THREE.AmbientLight(0x404040, 15);
this.scene.add(ambientLight);
this.animate();
}
private loadModel() {
const loader = new GLTFLoader();
loader.load(
'model/Kohmi_v3_waving_export.glb',
(gltf) => {
console.log('Model loaded:', gltf);
const robot = gltf.scene;
this.scene.add(robot);
// Position the robot
robot.position.set(0, 0, 0);
// Rotate the robot to face the camera
robot.rotation.y = Math.PI / 2 + Math.PI;
// Set up animation
if (gltf.animations.length > 0) {
this.mixer = new THREE.AnimationMixer(robot);
this.animationAction = this.mixer.clipAction(gltf.animations[0]);
this.animationAction.setLoop(THREE.LoopOnce, 1);
this.animationAction.clampWhenFinished = true;
}
// Add event listener for keypress
document.addEventListener('keydown', this.onKeyDown.bind(this));
},
function (xhr) {
console.log((xhr.loaded / xhr.total) * 100 + '% loaded');
},
function (error) {
console.error('An error occurred:', error);
}
);
}
private animate() {
requestAnimationFrame(() => this.animate());
if (this.mixer) {
this.mixer.update(0.016); // Update mixer (assuming 60fps)
}
this.renderer.render(this.scene, this.camera);
}
private onKeyDown(event: KeyboardEvent) {
if (
event.key === 'k' &&
this.animationAction &&
!this.animationAction.isRunning()
) {
this.animationAction.reset().play();
}
}
}
Another weird thing is that when I remove the chat component and make the component with the model in it full width (or just dont specify any width) it gets stretched too, but when I load the model like this in a normal JS ThreeJS project it works perfectly fine… so maybe it has to do with Angular? Or did I miss to specify anything in my implementation?