use glTF animations
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+71
-38
@@ -23,20 +23,9 @@ camera.updateProjectionMatrix()
|
|||||||
camera.layers.enable(1)
|
camera.layers.enable(1)
|
||||||
camera.layers.enable(2)
|
camera.layers.enable(2)
|
||||||
|
|
||||||
const gltfLoader = new GLTFLoader()
|
|
||||||
const addTo = function addTo(scene) {
|
|
||||||
return function addToScene(gltf) {
|
|
||||||
const model = gltf.scene
|
|
||||||
const scale = 2
|
|
||||||
model.scale.set(scale, scale, scale)
|
|
||||||
scene.add(model)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const projectileMaterial = new THREE.MeshToonMaterial({ color: 0xcccccc })
|
const projectileMaterial = new THREE.MeshToonMaterial({ color: 0xcccccc })
|
||||||
const terrainMaterial = new THREE.MeshToonMaterial({ color: 0x5c4033 })
|
const terrainMaterial = new THREE.MeshToonMaterial({ color: 0x5c4033 })
|
||||||
const passableTerrainMaterial = new THREE.MeshToonMaterial({ color: 0x228822, transparent: true, opacity: 0.65 })
|
const passableTerrainMaterial = new THREE.MeshToonMaterial({ color: 0x228822, transparent: true, opacity: 0.65 })
|
||||||
// const bboxMaterial = new THREE.MeshToonMaterial({ color: 0xffd700, transparent: true, opacity: 0.2 })
|
|
||||||
const opacity = 0.3
|
const opacity = 0.3
|
||||||
const teamMaterials = {
|
const teamMaterials = {
|
||||||
blue: new THREE.MeshToonMaterial({ color: 0x4444ff }),
|
blue: new THREE.MeshToonMaterial({ color: 0x4444ff }),
|
||||||
@@ -58,17 +47,50 @@ minimapRenderer.setSize(300, 300)
|
|||||||
minimapRenderer.setAnimationLoop(minimapRender)
|
minimapRenderer.setAnimationLoop(minimapRender)
|
||||||
minimapCamera.position.set(10, 10, 10)
|
minimapCamera.position.set(10, 10, 10)
|
||||||
|
|
||||||
|
const animationActions = {}
|
||||||
const entities = {}
|
const entities = {}
|
||||||
const projectiles = {}
|
const gltf = {}
|
||||||
|
const mixers = {}
|
||||||
const positionTweens = {}
|
const positionTweens = {}
|
||||||
|
const projectiles = {}
|
||||||
const rotationTweens = {}
|
const rotationTweens = {}
|
||||||
const terrains = {}
|
const terrains = {}
|
||||||
var state = { abilities: [], entities: [], terrains: [], projectiles: [] }
|
var state = { abilities: [], entities: [], terrains: [], projectiles: [] }
|
||||||
|
|
||||||
|
global.animationActions = animationActions
|
||||||
global.entities = entities
|
global.entities = entities
|
||||||
|
global.gltf = gltf
|
||||||
|
global.mixers = mixers
|
||||||
global.projectiles = projectiles
|
global.projectiles = projectiles
|
||||||
global.terrains = terrains
|
|
||||||
global.state = state
|
global.state = state
|
||||||
|
global.terrains = terrains
|
||||||
|
|
||||||
|
const gltfLoader = new GLTFLoader()
|
||||||
|
const preloadGLTF = function loadTemplate(path) {
|
||||||
|
gltfLoader.load(path, (loadedGLTF) => gltf[path] = loadedGLTF)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addGLTF = function addGLTF(scene, path, id) {
|
||||||
|
if (gltf[path] == null) {
|
||||||
|
setTimeout(() => addGLTF(scene, path, id), 200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const scale = 2
|
||||||
|
const model = gltf[path].scene.clone()
|
||||||
|
|
||||||
|
const mixer = new THREE.AnimationMixer(model)
|
||||||
|
mixers[id] = mixer
|
||||||
|
|
||||||
|
animationActions[id] = {}
|
||||||
|
gltf[path].animations.forEach((it) => {
|
||||||
|
const animation = mixer.clipAction(it)
|
||||||
|
animationActions[id][it.name] = animation
|
||||||
|
})
|
||||||
|
|
||||||
|
model.scale.set(scale, scale, scale)
|
||||||
|
scene.add(model)
|
||||||
|
}
|
||||||
|
|
||||||
const geometry = new THREE.PlaneGeometry(0, 0)
|
const geometry = new THREE.PlaneGeometry(0, 0)
|
||||||
const material = new THREE.MeshToonMaterial({ color: 0x115011 })
|
const material = new THREE.MeshToonMaterial({ color: 0x115011 })
|
||||||
@@ -102,6 +124,7 @@ function render() {
|
|||||||
cameraMovement()
|
cameraMovement()
|
||||||
Object.values(positionTweens).forEach((tween) => tween.update())
|
Object.values(positionTweens).forEach((tween) => tween.update())
|
||||||
Object.values(rotationTweens).forEach((tween) => tween.update())
|
Object.values(rotationTweens).forEach((tween) => tween.update())
|
||||||
|
Object.values(mixers).forEach((mixer) => mixer.update(delta))
|
||||||
renderer.render(scene, camera)
|
renderer.render(scene, camera)
|
||||||
stats.end()
|
stats.end()
|
||||||
}
|
}
|
||||||
@@ -311,6 +334,7 @@ function connectWebSocket() {
|
|||||||
else {
|
else {
|
||||||
// const entityMaterial = teamMaterials[e.team]
|
// const entityMaterial = teamMaterials[e.team]
|
||||||
// entity = new THREE.Mesh(new THREE.CylinderGeometry(e.visualRadius / 100, e.visualRadius / 100, e.height / 50), entityMaterial)
|
// entity = new THREE.Mesh(new THREE.CylinderGeometry(e.visualRadius / 100, e.visualRadius / 100, e.height / 50), entityMaterial)
|
||||||
|
// TODO: change entity material
|
||||||
entity = new THREE.Group()
|
entity = new THREE.Group()
|
||||||
entity.rotation.x = Math.PI / 2
|
entity.rotation.x = Math.PI / 2
|
||||||
entity.scale.set(e.visualRadius / 100, e.height / 100, e.visualRadius / 100)
|
entity.scale.set(e.visualRadius / 100, e.height / 100, e.visualRadius / 100)
|
||||||
@@ -319,9 +343,9 @@ function connectWebSocket() {
|
|||||||
entity.position.set(e.position.x / 100, e.position.y / 100, 0)
|
entity.position.set(e.position.x / 100, e.position.y / 100, 0)
|
||||||
scene.add(entity)
|
scene.add(entity)
|
||||||
|
|
||||||
const hpMargin = 0.4
|
const hpMargin = 4.8
|
||||||
const maxHp = new THREE.Sprite(new THREE.SpriteMaterial({ color: 0xd03333 }))
|
const maxHp = new THREE.Sprite(new THREE.SpriteMaterial({ color: 0xd03333 }))
|
||||||
maxHp.position.set(0, (e.visualRadius / 100) + hpMargin, 0)
|
maxHp.position.set(0, hpMargin, 0)
|
||||||
maxHp.scale.set(1.5, 0.2, 1)
|
maxHp.scale.set(1.5, 0.2, 1)
|
||||||
maxHp.layers.set(1)
|
maxHp.layers.set(1)
|
||||||
entity.add(maxHp)
|
entity.add(maxHp)
|
||||||
@@ -338,9 +362,9 @@ function connectWebSocket() {
|
|||||||
entity.add(teamMarker)
|
entity.add(teamMarker)
|
||||||
|
|
||||||
const buffMaterial = new THREE.MeshToonMaterial({ color: 0xffff00, transparent: true, opacity: 0.4 })
|
const buffMaterial = new THREE.MeshToonMaterial({ color: 0xffff00, transparent: true, opacity: 0.4 })
|
||||||
const buffMarker = new THREE.Mesh(new THREE.CylinderGeometry((e.visualRadius + 10) / 100, (e.visualRadius + 10) / 100, 1), buffMaterial)
|
const buffMarkerRadius = 0.6
|
||||||
const buffMarkerSize = 400
|
const buffMarker = new THREE.Mesh(new THREE.CylinderGeometry(buffMarkerRadius, buffMarkerRadius, 0.3), buffMaterial)
|
||||||
buffMarker.scale.y = e.height / buffMarkerSize
|
buffMarker.position.y = 2
|
||||||
buffMarker.layers.set(1)
|
buffMarker.layers.set(1)
|
||||||
buffMarker.visible = false
|
buffMarker.visible = false
|
||||||
entity.add(buffMarker)
|
entity.add(buffMarker)
|
||||||
@@ -356,7 +380,7 @@ function connectWebSocket() {
|
|||||||
castingMarker.position.y = 1
|
castingMarker.position.y = 1
|
||||||
castingMarker.scale.y = e.height / castingMarkerSize
|
castingMarker.scale.y = e.height / castingMarkerSize
|
||||||
castingMarker.layers.set(1)
|
castingMarker.layers.set(1)
|
||||||
buffMarker.visible = false
|
castingMarker.visible = false
|
||||||
castingMarkerrotationBase.add(castingMarker)
|
castingMarkerrotationBase.add(castingMarker)
|
||||||
|
|
||||||
const rangeMaterial = teamMaterials['range']
|
const rangeMaterial = teamMaterials['range']
|
||||||
@@ -369,10 +393,15 @@ function connectWebSocket() {
|
|||||||
entity.add(rangeMarker)
|
entity.add(rangeMarker)
|
||||||
|
|
||||||
const modelRotationBase = new THREE.Object3D()
|
const modelRotationBase = new THREE.Object3D()
|
||||||
|
modelRotationBase.rotation.y = e.rotation - (Math.PI / 2)
|
||||||
modelRotationBase.layers.set(1)
|
modelRotationBase.layers.set(1)
|
||||||
entity.add(modelRotationBase)
|
entity.add(modelRotationBase)
|
||||||
|
|
||||||
gltfLoader.load('models/generic-player-placeholder.gltf', addTo(modelRotationBase))
|
addGLTF(modelRotationBase, 'models/generic-player-placeholder.gltf', e.id)
|
||||||
|
const animations = animationActions[e.id] ?? {}
|
||||||
|
if (e.dead) {
|
||||||
|
animations.dead?.reset().play()
|
||||||
|
}
|
||||||
|
|
||||||
entities[e.id] = entity
|
entities[e.id] = entity
|
||||||
}
|
}
|
||||||
@@ -381,18 +410,30 @@ function connectWebSocket() {
|
|||||||
entity.children.at(1).visible = !e.dead
|
entity.children.at(1).visible = !e.dead
|
||||||
entity.children.at(2).visible = e.buffs.some((it) => it.id == 'exposed') // TODO: only works for Exposed now
|
entity.children.at(2).visible = e.buffs.some((it) => it.id == 'exposed') // TODO: only works for Exposed now
|
||||||
|
|
||||||
|
const animations = animationActions[e.id] ?? {}
|
||||||
|
const fadeIn = 0.15
|
||||||
|
|
||||||
if (e.dead) {
|
if (e.dead) {
|
||||||
entity.rotation.x = 0
|
if (!animations.dead?.isRunning()) {
|
||||||
entity.children.at(5).children.at(0).rotation.y = Math.PI / 2
|
Object.values(animations).forEach((it) => it.isRunning() && it.reset().fadeOut(fadeIn).play())
|
||||||
|
animations.dead?.reset().fadeIn(fadeIn).play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (e.casting != null) {
|
||||||
|
if (!animations.cast?.isRunning()) {
|
||||||
|
Object.values(animations).forEach((it) => it.isRunning() && it.reset().fadeOut(fadeIn).play())
|
||||||
|
animations.cast?.reset().fadeIn(fadeIn).play()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
entity.rotation.x = Math.PI / 2
|
if (!animations.default?.isRunning()) {
|
||||||
entity.children.at(5).children.at(0).rotation.y = 0
|
Object.values(animations).forEach((it) => it.isRunning() && it.reset().fadeOut(fadeIn).play())
|
||||||
|
animations.default?.reset().fadeIn(fadeIn).play()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.userData.flaggedForRemoval = false
|
entity.userData.flaggedForRemoval = false
|
||||||
entity.children.at(3).rotation.y = e.rotation
|
entity.children.at(3).rotation.y = e.rotation
|
||||||
// entity.children.at(5).rotation.y = e.rotation - (Math.PI / 2)
|
|
||||||
const oldRotationY = entity.children.at(5).rotation.y
|
const oldRotationY = entity.children.at(5).rotation.y
|
||||||
const newRotationY = e.rotation - (Math.PI / 2)
|
const newRotationY = e.rotation - (Math.PI / 2)
|
||||||
if (Math.abs((oldRotationY - (2 * Math.PI)) - newRotationY) < Math.abs(oldRotationY - newRotationY)) {
|
if (Math.abs((oldRotationY - (2 * Math.PI)) - newRotationY) < Math.abs(oldRotationY - newRotationY)) {
|
||||||
@@ -411,13 +452,15 @@ function connectWebSocket() {
|
|||||||
hp.position.x = -(1 - percentageHp) / 2
|
hp.position.x = -(1 - percentageHp) / 2
|
||||||
|
|
||||||
entity.children.at(4).visible = e.id == playerId
|
entity.children.at(4).visible = e.id == playerId
|
||||||
entity.children.at(3).children.at(0).visible = e.casting != null
|
// entity.children.at(3).children.at(0).visible = e.casting != null
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const e of Object.values(entities)) {
|
for (const e of Object.values(entities)) {
|
||||||
if (e.userData.flaggedForRemoval) {
|
if (e.userData.flaggedForRemoval) {
|
||||||
scene.remove(e)
|
scene.remove(e)
|
||||||
|
delete animationActions[e.userData.id]
|
||||||
delete entities[e.userData.id]
|
delete entities[e.userData.id]
|
||||||
|
delete mixers[e.userData.id]
|
||||||
delete positionTweens[e.userData.id]
|
delete positionTweens[e.userData.id]
|
||||||
delete rotationTweens[e.userData.id]
|
delete rotationTweens[e.userData.id]
|
||||||
}
|
}
|
||||||
@@ -480,18 +523,6 @@ function connectWebSocket() {
|
|||||||
terrain.userData.id = t.id
|
terrain.userData.id = t.id
|
||||||
scene.add(terrain)
|
scene.add(terrain)
|
||||||
terrains[t.id] = terrain
|
terrains[t.id] = terrain
|
||||||
|
|
||||||
// // TODO: bboxes aren't tracked and can leak memory
|
|
||||||
// const bboxValues = Object.values(t.bbox)
|
|
||||||
// if (bboxValues.length >= 4) {
|
|
||||||
// const width = (bboxValues[1] - bboxValues[3]) / 100
|
|
||||||
// const height = (bboxValues[0] - bboxValues[2]) / 100
|
|
||||||
|
|
||||||
// const bbox = new THREE.Mesh(new THREE.BoxGeometry(width, height, 0.2), bboxMaterial)
|
|
||||||
// bbox.position.set((bboxValues[3] / 100) + (width / 2), (bboxValues[2] / 100) + (height / 2), 0)
|
|
||||||
// bbox.layers.set(1)
|
|
||||||
// scene.add(bbox)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
terrain.position.set(t.position.x / 100, t.position.y / 100, 0)
|
terrain.position.set(t.position.x / 100, t.position.y / 100, 0)
|
||||||
@@ -582,6 +613,8 @@ function connectWebSocket() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
|
preloadGLTF('models/generic-player-placeholder.gltf')
|
||||||
|
|
||||||
const params = Object.fromEntries(new URLSearchParams(window.location.search).entries())
|
const params = Object.fromEntries(new URLSearchParams(window.location.search).entries())
|
||||||
playerId = params.id
|
playerId = params.id
|
||||||
if (playerId == null) {
|
if (playerId == null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user