Dies ist das Repository meines kleinen Portfolios.
Im Hintergrund läuft eine Planetensimulation, geschrieben in JavaScript und Three.js.
Die zu sehenden Texturen stammen von:
https://www.solarsystemscope.com/textures/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
494 lines
12 KiB
494 lines
12 KiB
( function () { |
|
|
|
class MD2CharacterComplex { |
|
|
|
constructor() { |
|
|
|
this.scale = 1; // animation parameters |
|
|
|
this.animationFPS = 6; |
|
this.transitionFrames = 15; // movement model parameters |
|
|
|
this.maxSpeed = 275; |
|
this.maxReverseSpeed = - 275; |
|
this.frontAcceleration = 600; |
|
this.backAcceleration = 600; |
|
this.frontDecceleration = 600; |
|
this.angularSpeed = 2.5; // rig |
|
|
|
this.root = new THREE.Object3D(); |
|
this.meshBody = null; |
|
this.meshWeapon = null; |
|
this.controls = null; // skins |
|
|
|
this.skinsBody = []; |
|
this.skinsWeapon = []; |
|
this.weapons = []; |
|
this.currentSkin = undefined; // |
|
|
|
this.onLoadComplete = function () {}; // internals |
|
|
|
|
|
this.meshes = []; |
|
this.animations = {}; |
|
this.loadCounter = 0; // internal movement control variables |
|
|
|
this.speed = 0; |
|
this.bodyOrientation = 0; |
|
this.walkSpeed = this.maxSpeed; |
|
this.crouchSpeed = this.maxSpeed * 0.5; // internal animation parameters |
|
|
|
this.activeAnimation = null; |
|
this.oldAnimation = null; // API |
|
|
|
} |
|
|
|
enableShadows( enable ) { |
|
|
|
for ( let i = 0; i < this.meshes.length; i ++ ) { |
|
|
|
this.meshes[ i ].castShadow = enable; |
|
this.meshes[ i ].receiveShadow = enable; |
|
|
|
} |
|
|
|
} |
|
|
|
setVisible( enable ) { |
|
|
|
for ( let i = 0; i < this.meshes.length; i ++ ) { |
|
|
|
this.meshes[ i ].visible = enable; |
|
this.meshes[ i ].visible = enable; |
|
|
|
} |
|
|
|
} |
|
|
|
shareParts( original ) { |
|
|
|
this.animations = original.animations; |
|
this.walkSpeed = original.walkSpeed; |
|
this.crouchSpeed = original.crouchSpeed; |
|
this.skinsBody = original.skinsBody; |
|
this.skinsWeapon = original.skinsWeapon; // BODY |
|
|
|
const mesh = this._createPart( original.meshBody.geometry, this.skinsBody[ 0 ] ); |
|
|
|
mesh.scale.set( this.scale, this.scale, this.scale ); |
|
this.root.position.y = original.root.position.y; |
|
this.root.add( mesh ); |
|
this.meshBody = mesh; |
|
this.meshes.push( mesh ); // WEAPONS |
|
|
|
for ( let i = 0; i < original.weapons.length; i ++ ) { |
|
|
|
const meshWeapon = this._createPart( original.weapons[ i ].geometry, this.skinsWeapon[ i ] ); |
|
|
|
meshWeapon.scale.set( this.scale, this.scale, this.scale ); |
|
meshWeapon.visible = false; |
|
meshWeapon.name = original.weapons[ i ].name; |
|
this.root.add( meshWeapon ); |
|
this.weapons[ i ] = meshWeapon; |
|
this.meshWeapon = meshWeapon; |
|
this.meshes.push( meshWeapon ); |
|
|
|
} |
|
|
|
} |
|
|
|
loadParts( config ) { |
|
|
|
const scope = this; |
|
|
|
function loadTextures( baseUrl, textureUrls ) { |
|
|
|
const textureLoader = new THREE.TextureLoader(); |
|
const textures = []; |
|
|
|
for ( let i = 0; i < textureUrls.length; i ++ ) { |
|
|
|
textures[ i ] = textureLoader.load( baseUrl + textureUrls[ i ], checkLoadingComplete ); |
|
textures[ i ].mapping = THREE.UVMapping; |
|
textures[ i ].name = textureUrls[ i ]; |
|
textures[ i ].encoding = THREE.sRGBEncoding; |
|
|
|
} |
|
|
|
return textures; |
|
|
|
} |
|
|
|
function checkLoadingComplete() { |
|
|
|
scope.loadCounter -= 1; |
|
if ( scope.loadCounter === 0 ) scope.onLoadComplete(); |
|
|
|
} |
|
|
|
this.animations = config.animations; |
|
this.walkSpeed = config.walkSpeed; |
|
this.crouchSpeed = config.crouchSpeed; |
|
this.loadCounter = config.weapons.length * 2 + config.skins.length + 1; |
|
const weaponsTextures = []; |
|
|
|
for ( let i = 0; i < config.weapons.length; i ++ ) weaponsTextures[ i ] = config.weapons[ i ][ 1 ]; // SKINS |
|
|
|
|
|
this.skinsBody = loadTextures( config.baseUrl + 'skins/', config.skins ); |
|
this.skinsWeapon = loadTextures( config.baseUrl + 'skins/', weaponsTextures ); // BODY |
|
|
|
const loader = new THREE.MD2Loader(); |
|
loader.load( config.baseUrl + config.body, function ( geo ) { |
|
|
|
const boundingBox = new THREE.Box3(); |
|
boundingBox.setFromBufferAttribute( geo.attributes.position ); |
|
scope.root.position.y = - scope.scale * boundingBox.min.y; |
|
|
|
const mesh = scope._createPart( geo, scope.skinsBody[ 0 ] ); |
|
|
|
mesh.scale.set( scope.scale, scope.scale, scope.scale ); |
|
scope.root.add( mesh ); |
|
scope.meshBody = mesh; |
|
scope.meshes.push( mesh ); |
|
checkLoadingComplete(); |
|
|
|
} ); // WEAPONS |
|
|
|
const generateCallback = function ( index, name ) { |
|
|
|
return function ( geo ) { |
|
|
|
const mesh = scope._createPart( geo, scope.skinsWeapon[ index ] ); |
|
|
|
mesh.scale.set( scope.scale, scope.scale, scope.scale ); |
|
mesh.visible = false; |
|
mesh.name = name; |
|
scope.root.add( mesh ); |
|
scope.weapons[ index ] = mesh; |
|
scope.meshWeapon = mesh; |
|
scope.meshes.push( mesh ); |
|
checkLoadingComplete(); |
|
|
|
}; |
|
|
|
}; |
|
|
|
for ( let i = 0; i < config.weapons.length; i ++ ) { |
|
|
|
loader.load( config.baseUrl + config.weapons[ i ][ 0 ], generateCallback( i, config.weapons[ i ][ 0 ] ) ); |
|
|
|
} |
|
|
|
} |
|
|
|
setPlaybackRate( rate ) { |
|
|
|
if ( this.meshBody ) this.meshBody.duration = this.meshBody.baseDuration / rate; |
|
if ( this.meshWeapon ) this.meshWeapon.duration = this.meshWeapon.baseDuration / rate; |
|
|
|
} |
|
|
|
setWireframe( wireframeEnabled ) { |
|
|
|
if ( wireframeEnabled ) { |
|
|
|
if ( this.meshBody ) this.meshBody.material = this.meshBody.materialWireframe; |
|
if ( this.meshWeapon ) this.meshWeapon.material = this.meshWeapon.materialWireframe; |
|
|
|
} else { |
|
|
|
if ( this.meshBody ) this.meshBody.material = this.meshBody.materialTexture; |
|
if ( this.meshWeapon ) this.meshWeapon.material = this.meshWeapon.materialTexture; |
|
|
|
} |
|
|
|
} |
|
|
|
setSkin( index ) { |
|
|
|
if ( this.meshBody && this.meshBody.material.wireframe === false ) { |
|
|
|
this.meshBody.material.map = this.skinsBody[ index ]; |
|
this.currentSkin = index; |
|
|
|
} |
|
|
|
} |
|
|
|
setWeapon( index ) { |
|
|
|
for ( let i = 0; i < this.weapons.length; i ++ ) this.weapons[ i ].visible = false; |
|
|
|
const activeWeapon = this.weapons[ index ]; |
|
|
|
if ( activeWeapon ) { |
|
|
|
activeWeapon.visible = true; |
|
this.meshWeapon = activeWeapon; |
|
|
|
if ( this.activeAnimation ) { |
|
|
|
activeWeapon.playAnimation( this.activeAnimation ); |
|
this.meshWeapon.setAnimationTime( this.activeAnimation, this.meshBody.getAnimationTime( this.activeAnimation ) ); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
setAnimation( animationName ) { |
|
|
|
if ( animationName === this.activeAnimation || ! animationName ) return; |
|
|
|
if ( this.meshBody ) { |
|
|
|
this.meshBody.setAnimationWeight( animationName, 0 ); |
|
this.meshBody.playAnimation( animationName ); |
|
this.oldAnimation = this.activeAnimation; |
|
this.activeAnimation = animationName; |
|
this.blendCounter = this.transitionFrames; |
|
|
|
} |
|
|
|
if ( this.meshWeapon ) { |
|
|
|
this.meshWeapon.setAnimationWeight( animationName, 0 ); |
|
this.meshWeapon.playAnimation( animationName ); |
|
|
|
} |
|
|
|
} |
|
|
|
update( delta ) { |
|
|
|
if ( this.controls ) this.updateMovementModel( delta ); |
|
|
|
if ( this.animations ) { |
|
|
|
this.updateBehaviors(); |
|
this.updateAnimations( delta ); |
|
|
|
} |
|
|
|
} |
|
|
|
updateAnimations( delta ) { |
|
|
|
let mix = 1; |
|
|
|
if ( this.blendCounter > 0 ) { |
|
|
|
mix = ( this.transitionFrames - this.blendCounter ) / this.transitionFrames; |
|
this.blendCounter -= 1; |
|
|
|
} |
|
|
|
if ( this.meshBody ) { |
|
|
|
this.meshBody.update( delta ); |
|
this.meshBody.setAnimationWeight( this.activeAnimation, mix ); |
|
this.meshBody.setAnimationWeight( this.oldAnimation, 1 - mix ); |
|
|
|
} |
|
|
|
if ( this.meshWeapon ) { |
|
|
|
this.meshWeapon.update( delta ); |
|
this.meshWeapon.setAnimationWeight( this.activeAnimation, mix ); |
|
this.meshWeapon.setAnimationWeight( this.oldAnimation, 1 - mix ); |
|
|
|
} |
|
|
|
} |
|
|
|
updateBehaviors() { |
|
|
|
const controls = this.controls; |
|
const animations = this.animations; |
|
let moveAnimation, idleAnimation; // crouch vs stand |
|
|
|
if ( controls.crouch ) { |
|
|
|
moveAnimation = animations[ 'crouchMove' ]; |
|
idleAnimation = animations[ 'crouchIdle' ]; |
|
|
|
} else { |
|
|
|
moveAnimation = animations[ 'move' ]; |
|
idleAnimation = animations[ 'idle' ]; |
|
|
|
} // actions |
|
|
|
|
|
if ( controls.jump ) { |
|
|
|
moveAnimation = animations[ 'jump' ]; |
|
idleAnimation = animations[ 'jump' ]; |
|
|
|
} |
|
|
|
if ( controls.attack ) { |
|
|
|
if ( controls.crouch ) { |
|
|
|
moveAnimation = animations[ 'crouchAttack' ]; |
|
idleAnimation = animations[ 'crouchAttack' ]; |
|
|
|
} else { |
|
|
|
moveAnimation = animations[ 'attack' ]; |
|
idleAnimation = animations[ 'attack' ]; |
|
|
|
} |
|
|
|
} // set animations |
|
|
|
|
|
if ( controls.moveForward || controls.moveBackward || controls.moveLeft || controls.moveRight ) { |
|
|
|
if ( this.activeAnimation !== moveAnimation ) { |
|
|
|
this.setAnimation( moveAnimation ); |
|
|
|
} |
|
|
|
} |
|
|
|
if ( Math.abs( this.speed ) < 0.2 * this.maxSpeed && ! ( controls.moveLeft || controls.moveRight || controls.moveForward || controls.moveBackward ) ) { |
|
|
|
if ( this.activeAnimation !== idleAnimation ) { |
|
|
|
this.setAnimation( idleAnimation ); |
|
|
|
} |
|
|
|
} // set animation direction |
|
|
|
|
|
if ( controls.moveForward ) { |
|
|
|
if ( this.meshBody ) { |
|
|
|
this.meshBody.setAnimationDirectionForward( this.activeAnimation ); |
|
this.meshBody.setAnimationDirectionForward( this.oldAnimation ); |
|
|
|
} |
|
|
|
if ( this.meshWeapon ) { |
|
|
|
this.meshWeapon.setAnimationDirectionForward( this.activeAnimation ); |
|
this.meshWeapon.setAnimationDirectionForward( this.oldAnimation ); |
|
|
|
} |
|
|
|
} |
|
|
|
if ( controls.moveBackward ) { |
|
|
|
if ( this.meshBody ) { |
|
|
|
this.meshBody.setAnimationDirectionBackward( this.activeAnimation ); |
|
this.meshBody.setAnimationDirectionBackward( this.oldAnimation ); |
|
|
|
} |
|
|
|
if ( this.meshWeapon ) { |
|
|
|
this.meshWeapon.setAnimationDirectionBackward( this.activeAnimation ); |
|
this.meshWeapon.setAnimationDirectionBackward( this.oldAnimation ); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
updateMovementModel( delta ) { |
|
|
|
function exponentialEaseOut( k ) { |
|
|
|
return k === 1 ? 1 : - Math.pow( 2, - 10 * k ) + 1; |
|
|
|
} |
|
|
|
const controls = this.controls; // speed based on controls |
|
|
|
if ( controls.crouch ) this.maxSpeed = this.crouchSpeed; else this.maxSpeed = this.walkSpeed; |
|
this.maxReverseSpeed = - this.maxSpeed; |
|
if ( controls.moveForward ) this.speed = THREE.MathUtils.clamp( this.speed + delta * this.frontAcceleration, this.maxReverseSpeed, this.maxSpeed ); |
|
if ( controls.moveBackward ) this.speed = THREE.MathUtils.clamp( this.speed - delta * this.backAcceleration, this.maxReverseSpeed, this.maxSpeed ); // orientation based on controls |
|
// (don't just stand while turning) |
|
|
|
const dir = 1; |
|
|
|
if ( controls.moveLeft ) { |
|
|
|
this.bodyOrientation += delta * this.angularSpeed; |
|
this.speed = THREE.MathUtils.clamp( this.speed + dir * delta * this.frontAcceleration, this.maxReverseSpeed, this.maxSpeed ); |
|
|
|
} |
|
|
|
if ( controls.moveRight ) { |
|
|
|
this.bodyOrientation -= delta * this.angularSpeed; |
|
this.speed = THREE.MathUtils.clamp( this.speed + dir * delta * this.frontAcceleration, this.maxReverseSpeed, this.maxSpeed ); |
|
|
|
} // speed decay |
|
|
|
|
|
if ( ! ( controls.moveForward || controls.moveBackward ) ) { |
|
|
|
if ( this.speed > 0 ) { |
|
|
|
const k = exponentialEaseOut( this.speed / this.maxSpeed ); |
|
this.speed = THREE.MathUtils.clamp( this.speed - k * delta * this.frontDecceleration, 0, this.maxSpeed ); |
|
|
|
} else { |
|
|
|
const k = exponentialEaseOut( this.speed / this.maxReverseSpeed ); |
|
this.speed = THREE.MathUtils.clamp( this.speed + k * delta * this.backAcceleration, this.maxReverseSpeed, 0 ); |
|
|
|
} |
|
|
|
} // displacement |
|
|
|
|
|
const forwardDelta = this.speed * delta; |
|
this.root.position.x += Math.sin( this.bodyOrientation ) * forwardDelta; |
|
this.root.position.z += Math.cos( this.bodyOrientation ) * forwardDelta; // steering |
|
|
|
this.root.rotation.y = this.bodyOrientation; |
|
|
|
} // internal |
|
|
|
|
|
_createPart( geometry, skinMap ) { |
|
|
|
const materialWireframe = new THREE.MeshLambertMaterial( { |
|
color: 0xffaa00, |
|
wireframe: true |
|
} ); |
|
const materialTexture = new THREE.MeshLambertMaterial( { |
|
color: 0xffffff, |
|
wireframe: false, |
|
map: skinMap |
|
} ); // |
|
|
|
const mesh = new THREE.MorphBlendMesh( geometry, materialTexture ); |
|
mesh.rotation.y = - Math.PI / 2; // |
|
|
|
mesh.materialTexture = materialTexture; |
|
mesh.materialWireframe = materialWireframe; // |
|
|
|
mesh.autoCreateAnimations( this.animationFPS ); |
|
return mesh; |
|
|
|
} |
|
|
|
} |
|
|
|
THREE.MD2CharacterComplex = MD2CharacterComplex; |
|
|
|
} )();
|
|
|