Refactored the WHOLE project

From a simple test file, we're now on road to create a real project
Took 3 hours, but code looks nice
This commit is contained in:
2023-11-15 17:23:11 +01:00
parent c73aa99ca2
commit d15d257ddb
8 changed files with 191 additions and 133 deletions

View File

@@ -0,0 +1,14 @@
import * as THREE from 'three';
import * as MathUtils from 'three/src/math/MathUtils.js';
export function getCamera(){
/* Function to create and place the Camera object. is freecam is true, it will enable freecam controls for easier debug*/
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.x = 0;// <----->
camera.position.y = -4;// ^^ / vv
camera.position.z = 4;// ^-----v
camera.rotateOnWorldAxis(new THREE.Vector3(1, 0, 0), MathUtils.degToRad(60));
return camera;
}

View File

@@ -1,17 +0,0 @@
//https://medium.com/geekculture/how-to-control-three-js-camera-like-a-pro-a8575a717a2
import * as THREE from 'three';
import { FlyControls } from 'three/examples/jsm/controls/FlyControls';
let controls;
export function enableFreeCam(camera, renderer){
controls = new FlyControls( camera, renderer.domElement );
controls.movementSpeed = 5;
controls.rollSpeed = Math.PI / 48;
controls.autoForward = false;
controls.dragToLook = true;
}
export function updateFreeCamKeys(){
controls.update(0.01)
}

View File

@@ -1,128 +1,46 @@
import * as THREE from 'three'; import * as THREE from 'three';
import * as MathUtils from 'three/src/math/MathUtils.js'; import { getCamera } from './cameras/PerspectiveCamera.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; import {addAssetsOnScene} from './utils/addAssetsOnScene.js';
import {createGround} from './objects/ground.js'; import * as PlayerMovement from './utils/playerMovement.js';
import {alignGround} from './utils/placement.js'; import * as Animations from './utils/animateMesh.js';
import * as Freecam from './cameras/freecam.js';
let Clock = new THREE.Clock(); let Clock = new THREE.Clock();
console.log("OK")
/* --------------------- */
/* | SCENE CREATION | */
/* --------------------- */
const scene = new THREE.Scene(); const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0EB1D2); scene.background = new THREE.Color(0x0EB1D2);
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); const camera = getCamera();
const renderer = new THREE.WebGLRenderer(); const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight ); renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement ); document.body.appendChild( renderer.domElement );
//Freecam //Here, we create/load every asset used, and add it in the scene
Freecam.enableFreeCam(camera, renderer); let assets = await addAssetsOnScene(scene);
const ground = createGround();
scene.add(ground);
const loader = new GLTFLoader();
let mixer, clip, action, playerModel;
loader.load( './src/objects/models/player.gltf', function ( gltf ) {
playerModel = gltf;
gltf.scene.scale.set(0.2,0.2,0.2);
gltf.scene.rotateOnWorldAxis(new THREE.Vector3(1,0,0), MathUtils.degToRad(90));
alignGround(ground, gltf.scene);
scene.add( gltf.scene );
mixer = new THREE.AnimationMixer( gltf.scene );
clip = THREE.AnimationClip.findByName( gltf.animations, 'Walk' );
action = mixer.clipAction( clip );
}, undefined, function ( error ) {
console.error( error );
} );
scene.add(new THREE.AmbientLight(0x404040, 50));
camera.position.x = 0;// <----->
camera.position.y = -4;// ^^ / vv
camera.position.z = 4;// ^-----v
camera.rotateOnWorldAxis(new THREE.Vector3(1, 0, 0), MathUtils.degToRad(60));
/* ----- -*
MOVEMENT
-----------*/
const PLAYER_SPEED = 0.015;
const playerDirection = new THREE.Vector3(0, 0, 0);
const playerRotation = new THREE.Quaternion();
/* --------------------- */
/* | MOVEMENT | */
/* --------------------- */
// Events to listen keyboard inputs // Events to listen keyboard inputs
document.addEventListener('keydown', handleKeyDown); document.addEventListener('keydown', PlayerMovement.handleKeyDown);
document.addEventListener('keyup', handleKeyUp); document.addEventListener('keyup', PlayerMovement.handleKeyUp);
//Managing keys /* --------------------- */
function handleKeyDown(event) { /* | ANIMATIONS | */
action.play(); /* --------------------- */
switch (event.code) { //We can add an animationMixer on objects, and define ready to play actions on objects
case 'ArrowUp': //https://threejs.org/docs/#api/en/animation/AnimationAction
case 'KeyW': Animations.addAnimationMixerOnMesh(assets.player, ["Walk"]);
playerDirection.y = 1; // Up
break;
case 'ArrowDown':
case 'KeyS':
playerDirection.y = -1; // Back
break;
case 'ArrowLeft':
case 'KeyA':
playerDirection.x = -1; // Left
break;
case 'ArrowRight':
case 'KeyD':
playerDirection.x = 1; // Right
break;
}
}
function handleKeyUp(event) {
action.stop();
switch (event.code) {
case 'ArrowUp':
case 'KeyW':
case 'ArrowDown':
case 'KeyS':
playerDirection.y = 0; // Arrêter le mouvement vertical
break;
case 'ArrowLeft':
case 'KeyA':
case 'ArrowRight':
case 'KeyD':
playerDirection.x = 0; // Arrêter le mouvement horizontal
break;
}
}
function updatePlayerPosition() {
const delta = playerDirection.clone().multiplyScalar(PLAYER_SPEED);
playerModel.scene.position.add(delta);
camera.position.add(delta);
const ROTATION_SPEED = 0.05; // Vitesse de rotation
if (playerDirection.lengthSq() > 0) {
const angle = Math.atan2(playerDirection.x, -playerDirection.y);
const targetRotation = new THREE.Quaternion();
targetRotation.setFromAxisAngle(new THREE.Vector3(0, 1, 0), angle);
playerRotation.slerp(targetRotation, ROTATION_SPEED);
playerModel.scene.setRotationFromQuaternion(playerRotation);
playerModel.scene.rotateOnWorldAxis(new THREE.Vector3(1, 0, 0), MathUtils.degToRad(90));
}
}
function animate() { function animate() {
requestAnimationFrame( animate ); requestAnimationFrame( animate );
if(mixer) mixer.update( Clock.getDelta()*1.4 ); //If mixer ready
updatePlayerPosition(); PlayerMovement.updatePlayerPositionAnimation(assets.player, camera);//Updating player position and rotation
//Freecam.updateFreeCamKeys()//Freecam Animations.updateAnimations(Clock.getDelta());//Playing animations
renderer.render( scene, camera );
renderer.render( scene, camera );//Calculating and redering new frame
} }
animate(); animate();

View File

@@ -0,0 +1,37 @@
import * as THREE from 'three';
import * as MathUtils from 'three/src/math/MathUtils.js';
import { createGround } from '../objects/ground.js';
import {loadModel} from './import.js';
function alignGround(ground, MovingObject){
/*Function used to place an object on top of another object. In fact, this is used only to place correctly objects on the ground*/
const boundingBoxGround = new THREE.Box3().setFromObject(ground);
const boundingBoxMovingObject = new THREE.Box3().setFromObject(MovingObject);
const MovingObjectHeight = boundingBoxMovingObject.max.z - boundingBoxMovingObject.min.z;
MovingObject.position.y = MovingObjectHeight/2 + boundingBoxGround.max.z;
}
export async function addAssetsOnScene(scene){
/*This function takes the scene as arg, and is used to load everything needed on the terrain.
Player, trees, fences, and everything displayed on the screen is added by this function, at the right place
Will return an object containing created objects*/
scene.add(new THREE.AmbientLight(0x404040, 50));//Light, required to see
const ground = createGround();
scene.add(ground);
const player = await loadModel('src/objects/models/player.gltf');//Loading player
player.scene.scale.set(0.2,0.2,0.2);
alignGround(ground, player.scene);
player.scene.rotateOnWorldAxis(new THREE.Vector3(1,0,0), MathUtils.degToRad(90));//Must be standing up
scene.add( player.scene );
return {
ground: ground,
player: player,
};
}

28
src/utils/animateMesh.js Normal file
View File

@@ -0,0 +1,28 @@
import * as THREE from 'three';
const ANIMATION_SPEED_MULTIPLIER = 1.4;
let mixersToUpdateEachFrame = [];
export function addAnimationMixerOnMesh(mesh, actions){
//See https://threejs.org/docs/#manual/en/introduction/Animation-system
/*Function used to add an animationMixer on a mesh, so we can easily start or stop animations
Takes the animated mesh, and a list of actions names that should become animated */
const mixer = new THREE.AnimationMixer(mesh.scene);//We create a new mixer to manage every animations on this mesh
mesh.animationMixer = mixer;//We add this mixer on the mesh
mesh.animationActions = {};//And create an object that will hold actions
actions.forEach(element => {
const clip = THREE.AnimationClip.findByName( mesh.animations, element );//For each given action name, we get a clip
mesh.animationActions[element] = mixer.clipAction(clip);//and save it as an action
});
mixersToUpdateEachFrame.push(mixer);
}
export function updateAnimations(deltaSeconds){
mixersToUpdateEachFrame.forEach(element => {
element.update(deltaSeconds * ANIMATION_SPEED_MULTIPLIER);
});
};

18
src/utils/import.js Normal file
View File

@@ -0,0 +1,18 @@
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
export function loadModel(path) {
return new Promise((resolve, reject) => {
loader.load(
path,
(gltf) => {
resolve(gltf);
},
undefined,
(error) => {
reject(error);
}
);
});
}

View File

@@ -1,9 +0,0 @@
import * as THREE from 'three';
export function alignGround(ground, MovingObject){
const boundingBoxGround = new THREE.Box3().setFromObject(ground);
const boundingBoxMovingObject = new THREE.Box3().setFromObject(MovingObject);
const MovingObjectHeight = boundingBoxMovingObject.max.z - boundingBoxMovingObject.min.z;
MovingObject.position.y = MovingObjectHeight/2 + boundingBoxGround.max.z;
}

View File

@@ -0,0 +1,69 @@
import * as THREE from 'three';
import * as MathUtils from 'three/src/math/MathUtils.js';
const PLAYER_SPEED = 0.015;
const ROTATION_SPEED = 0.05; // Vitesse de rotation
const playerDirection = new THREE.Vector3(0, 0, 0);
const playerRotation = new THREE.Quaternion();
//Managing keys
export function handleKeyDown(event) {
switch (event.code) {
case 'ArrowUp':
case 'KeyW':
playerDirection.y = 1; // Up
break;
case 'ArrowDown':
case 'KeyS':
playerDirection.y = -1; // Back
break;
case 'ArrowLeft':
case 'KeyA':
playerDirection.x = -1; // Left
break;
case 'ArrowRight':
case 'KeyD':
playerDirection.x = 1; // Right
break;
}
}
export function handleKeyUp(event) {
switch (event.code) {
case 'ArrowUp':
case 'KeyW':
case 'ArrowDown':
case 'KeyS':
playerDirection.y = 0; // Arrêter le mouvement vertical
break;
case 'ArrowLeft':
case 'KeyA':
case 'ArrowRight':
case 'KeyD':
playerDirection.x = 0; // Arrêter le mouvement horizontal
break;
}
}
export function updatePlayerPositionAnimation(player, camera) {
/* Function called in the main loop
Used to calculate the position and orientation of the player
Also used to start and stop walking animation */
const delta = playerDirection.clone().multiplyScalar(PLAYER_SPEED);
player.scene.position.add(delta);
camera.position.add(delta);//The camera must follow the player
if (playerDirection.length() > 0) {//If a movement key is pressed ( or something in playerDirection )
if(!player.animationActions.Walk.isRunning()) player.animationActions.Walk.play();//Playing walk animation
const angle = Math.atan2(playerDirection.x, -playerDirection.y);//Get the angle between center (0;0) and the coordinates, which are directly provided by the pressed keys
const targetRotation = new THREE.Quaternion();
targetRotation.setFromAxisAngle(new THREE.Vector3(0, 1, 0), angle);
playerRotation.slerp(targetRotation, ROTATION_SPEED);//Slowly moving from playerRotation to targetRotation
player.scene.setRotationFromQuaternion(playerRotation);
player.scene.rotateOnWorldAxis(new THREE.Vector3(1, 0, 0), MathUtils.degToRad(90));//Used to force the player to be standing up instead of sleeping
}else{
if(player.animationActions.Walk.isRunning()) player.animationActions.Walk.stop();//Not walking, we can stop this animation
}
}