Skip to content

Commit

Permalink
[Viewer] Implement Player Animation Viewer (#12)
Browse files Browse the repository at this point in the history
* Implement Player animation viewer

* Update version
  • Loading branch information
Akkadius authored Jan 11, 2022
1 parent 45536b9 commit ea83811
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 11 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## [1.5.0]

### Player Animation Viewer

* Spire now has an player animation viewer that can be used standalone and in things like a Spell editor where there are casting and target animations when spells are casted
* Special thanks to DeadZergling for all of his effort putting together these high quality preview videos

[![](https://img.youtube.com/vi/_WLjso1d9p8/0.jpg)](https://www.youtube.com/watch?v=_WLjso1d9p8)

## [1.4.0]

### Emitter Viewer
Expand Down
1 change: 1 addition & 0 deletions frontend/src/app/asset-maps/player-animations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"type":"directory","name":"./assets/player-animations","contents":[{"name":"./assets/player-animations/101.mp4"},{"name":"./assets/player-animations/102.mp4"},{"name":"./assets/player-animations/103.mp4"},{"name":"./assets/player-animations/105.mp4"},{"name":"./assets/player-animations/106.mp4"},{"name":"./assets/player-animations/107.mp4"},{"name":"./assets/player-animations/108.mp4"},{"name":"./assets/player-animations/109.mp4"},{"name":"./assets/player-animations/10.mp4"},{"name":"./assets/player-animations/112.mp4"},{"name":"./assets/player-animations/118.mp4"},{"name":"./assets/player-animations/119.mp4"},{"name":"./assets/player-animations/11.mp4"},{"name":"./assets/player-animations/120.mp4"},{"name":"./assets/player-animations/122.mp4"},{"name":"./assets/player-animations/127.mp4"},{"name":"./assets/player-animations/128.mp4"},{"name":"./assets/player-animations/12.mp4"},{"name":"./assets/player-animations/13.mp4"},{"name":"./assets/player-animations/14.mp4"},{"name":"./assets/player-animations/157.mp4"},{"name":"./assets/player-animations/15.mp4"},{"name":"./assets/player-animations/162.mp4"},{"name":"./assets/player-animations/16.mp4"},{"name":"./assets/player-animations/170.mp4"},{"name":"./assets/player-animations/173.mp4"},{"name":"./assets/player-animations/178.mp4"},{"name":"./assets/player-animations/19.mp4"},{"name":"./assets/player-animations/1.mp4"},{"name":"./assets/player-animations/20.mp4"},{"name":"./assets/player-animations/21.mp4"},{"name":"./assets/player-animations/22.mp4"},{"name":"./assets/player-animations/23.mp4"},{"name":"./assets/player-animations/24.mp4"},{"name":"./assets/player-animations/25.mp4"},{"name":"./assets/player-animations/26.mp4"},{"name":"./assets/player-animations/27.mp4"},{"name":"./assets/player-animations/28.mp4"},{"name":"./assets/player-animations/29.mp4"},{"name":"./assets/player-animations/2.mp4"},{"name":"./assets/player-animations/30.mp4"},{"name":"./assets/player-animations/31.mp4"},{"name":"./assets/player-animations/33.mp4"},{"name":"./assets/player-animations/34.mp4"},{"name":"./assets/player-animations/35.mp4"},{"name":"./assets/player-animations/36.mp4"},{"name":"./assets/player-animations/37.mp4"},{"name":"./assets/player-animations/38.mp4"},{"name":"./assets/player-animations/39.mp4"},{"name":"./assets/player-animations/3.mp4"},{"name":"./assets/player-animations/40.mp4"},{"name":"./assets/player-animations/41.mp4"},{"name":"./assets/player-animations/42.mp4"},{"name":"./assets/player-animations/43.mp4"},{"name":"./assets/player-animations/44.mp4"},{"name":"./assets/player-animations/45.mp4"},{"name":"./assets/player-animations/46.mp4"},{"name":"./assets/player-animations/47.mp4"},{"name":"./assets/player-animations/48.mp4"},{"name":"./assets/player-animations/49.mp4"},{"name":"./assets/player-animations/4.mp4"},{"name":"./assets/player-animations/50.mp4"},{"name":"./assets/player-animations/51.mp4"},{"name":"./assets/player-animations/52.mp4"},{"name":"./assets/player-animations/53.mp4"},{"name":"./assets/player-animations/54.mp4"},{"name":"./assets/player-animations/55.mp4"},{"name":"./assets/player-animations/56.mp4"},{"name":"./assets/player-animations/57.mp4"},{"name":"./assets/player-animations/58.mp4"},{"name":"./assets/player-animations/59.mp4"},{"name":"./assets/player-animations/5.mp4"},{"name":"./assets/player-animations/60.mp4"},{"name":"./assets/player-animations/61.mp4"},{"name":"./assets/player-animations/62.mp4"},{"name":"./assets/player-animations/63.mp4"},{"name":"./assets/player-animations/64.mp4"},{"name":"./assets/player-animations/65.mp4"},{"name":"./assets/player-animations/66.mp4"},{"name":"./assets/player-animations/67.mp4"},{"name":"./assets/player-animations/68.mp4"},{"name":"./assets/player-animations/69.mp4"},{"name":"./assets/player-animations/6.mp4"},{"name":"./assets/player-animations/70.mp4"},{"name":"./assets/player-animations/73.mp4"},{"name":"./assets/player-animations/74.mp4"},{"name":"./assets/player-animations/75.mp4"},{"name":"./assets/player-animations/76.mp4"},{"name":"./assets/player-animations/77.mp4"},{"name":"./assets/player-animations/78.mp4"},{"name":"./assets/player-animations/7.mp4"},{"name":"./assets/player-animations/80.mp4"},{"name":"./assets/player-animations/81.mp4"},{"name":"./assets/player-animations/82.mp4"},{"name":"./assets/player-animations/83.mp4"},{"name":"./assets/player-animations/84.mp4"},{"name":"./assets/player-animations/85.mp4"},{"name":"./assets/player-animations/86.mp4"},{"name":"./assets/player-animations/87.mp4"},{"name":"./assets/player-animations/88.mp4"},{"name":"./assets/player-animations/89.mp4"},{"name":"./assets/player-animations/8.mp4"},{"name":"./assets/player-animations/90.mp4"},{"name":"./assets/player-animations/91.mp4"},{"name":"./assets/player-animations/92.mp4"},{"name":"./assets/player-animations/93.mp4"},{"name":"./assets/player-animations/94.mp4"},{"name":"./assets/player-animations/95.mp4"},{"name":"./assets/player-animations/96.mp4"},{"name":"./assets/player-animations/9.mp4"}]},{"type":"report","directories":0,"files":110}]
17 changes: 9 additions & 8 deletions frontend/src/components/layout/Navbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
</button>

<!-- Brand -->
<router-link class="ml-3 mt-3 d-none d-lg-block" to="/">
<img
src="~@/assets/img/eqemu-logo-1.png"
class="navbar-brand-img mx-auto d-none d-sm-block mb-3" alt="..."
style="max-height: 6rem"
>
</router-link>
<!-- <router-link class="ml-3 mt-3 d-none d-lg-block" to="/">-->
<!-- <img-->
<!-- src="~@/assets/img/eqemu-logo-1.png"-->
<!-- class="navbar-brand-img mx-auto d-none d-sm-block mb-3" alt="..."-->
<!-- style="max-height: 6rem"-->
<!-- >-->
<!-- </router-link>-->


<router-link class="ml-3 mt-3" to="/">
Expand Down Expand Up @@ -82,7 +82,7 @@
<!-- </form>-->

<!-- Heading -->
<h6 class="navbar-heading">
<h6 class="navbar-heading mt-3">
Tools
</h6>

Expand Down Expand Up @@ -260,6 +260,7 @@ export default {
{ title: "Race Viewer", to: ROUTE.RACE_VIEWER, icon: "ra ra-monster-skull mr-1" },
{ title: "Item Model Viewer", to: ROUTE.ITEM_VIEWER, icon: "ra ra-crossed-swords mr-1" },
{ title: "Item Icon Viewer", to: ROUTE.ITEM_ICON_VIEWER, icon: "ra ra-burning-eye mr-1" },
{ title: "Player Animations", to: ROUTE.PLAYER_ANIMATION_VIEWER, icon: "ra ra-player-dodge mr-1", isNew: true },
{ title: "Emitter Viewer", to: ROUTE.EMITTER_VIEWER, icon: "ra ra-droplet-splash mr-1", isNew: true },
{ title: "Spell Animation Viewer", to: ROUTE.SPELL_ANIMATION_VIEWER, icon: "ra ra-dragon mr-1" }
]
Expand Down
1 change: 1 addition & 0 deletions frontend/src/constants/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const App = {
ASSET_SPELL_ICONS_BASE_URL: ASSET_CDN_BASE_URL_INT + 'assets/spell_icons/',
ASSET_SPELL_ANIMATIONS: ASSET_CDN_BASE_URL_INT + 'assets/spell_animations/',
ASSET_EMITTER_CLIPS: ASSET_CDN_BASE_URL_INT + 'assets/emitters/',
ASSET_PLAYER_ANIMATION_CLIPS: ASSET_CDN_BASE_URL_INT + 'assets/player-animations/',
ASSET_EXPANSION_ICON_SMALL_URL: ASSET_CDN_BASE_URL_INT + 'assets/expansion-icons-small/',
ASSET_WALLPAPER_URL: ASSET_CDN_BASE_URL_INT + 'assets/wallpaper/',
ASSET_INVENTORY_SLOT_URL: ASSET_CDN_BASE_URL_INT + 'assets/inventory/',
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export default new Router({
component: () => import('./views/viewers/SpellAnimationViewer.vue'),
meta: {title: "Spell Animation Viewer"},
},
{
path: ROUTE.PLAYER_ANIMATION_VIEWER,
component: () => import('./views/viewers/PlayerAnimationViewer.vue'),
meta: {title: "Player Animation Viewer"},
},
{
path: ROUTE.EMITTER_VIEWER,
component: () => import('./views/viewers/EmitterViewer.vue'),
Expand Down
1 change: 1 addition & 0 deletions frontend/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const ROUTE = {
ITEM_ICON_VIEWER: "/item-icon-viewer",
SPELL_ANIMATION_VIEWER: "/spell-animation-viewer",
EMITTER_VIEWER: "/emitter-viewer",
PLAYER_ANIMATION_VIEWER: "/player-animation-viewer",
QUEST_API_EXPLORER: "/quest-api-explorer",
SPELLS_LIST: "/spells",
SPELL_EDIT: "/spell/%s",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/views/viewers/EmitterViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<h6 class="eq-header">{{ preview }}</h6>
</div>
</div>
<div class="mt-5">Videos courtesy of DeadZergling <3</div>
<div class="mt-3">Videos courtesy of DeadZergling <3</div>
</eq-window-simple>
</div>
</template>
Expand Down
267 changes: 267 additions & 0 deletions frontend/src/views/viewers/PlayerAnimationViewer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
<template>
<div :class="isComponent ? '' : 'container-fluid'">
<app-loader :is-loading="!loaded" padding="8"/>

<eq-window-simple
title="Environment Previews"
v-if="loaded"
class="mt-4 text-center"
>
<div v-if="filteredPreviews && filteredPreviews.length === 0">
No previews found...
</div>

<div v-for="(preview) in filteredPreviews" style="display:inline-block; position: relative;">
<video
muted
loop
:id="'preview-' + preview"
:data-src="previewBaseUrl + preview + '.mp4'"
class="player-anim-preview"
>
</video>
<div class="overlay">
<h6 class="eq-header">{{ preview }}</h6>
</div>
</div>
<div class="mt-3">Videos courtesy of DeadZergling <3</div>
</eq-window-simple>
</div>
</template>

<script>
import PageHeader from "@/components/layout/PageHeader";
import {App} from "@/constants/app";
import EqWindow from "@/components/eq-ui/EQWindow";
import Previews from "@/app/asset-maps/player-animations.json";
import {Listeners} from "@/app/listeners/listeners";
import {ROUTE} from "../../routes";
import EqWindowSimple from "../../components/eq-ui/EQWindowSimple";
let itemModels = [];
function handleRender() {
let playing = []
let stopping = []
let videos = document.getElementsByClassName("player-anim-preview");
for (let i = 0; i < videos.length; i++) {
let video = videos.item(i)
let source = document.createElement("source");
let dataSrc = video.getAttribute("data-src")
// Toggle playing
if (elementInViewport(video)) {
if (dataSrc) {
// video.setAttribute("src", dataSrc);
video.removeAttribute("data-src");
video.pause()
video.innerHTML = "";
video.removeAttribute("src");
source.setAttribute("src", dataSrc);
source.setAttribute("type", "video/mp4");
video.appendChild(source);
video.load();
video.play();
}
if (!videoPlaying(video) && videoLoaded(video)) {
video.play()
playing.push(video.getAttribute("id"))
}
} else {
if (videoPlaying(video) && videoLoaded(video)) {
video.pause()
stopping.push(video.getAttribute("id"))
}
}
}
console.log("Playing", playing)
console.log("Stopping", stopping)
}
function elementInViewport(el) {
let top = el.offsetTop;
let left = el.offsetLeft;
let width = el.offsetWidth;
let height = el.offsetHeight;
while (el.offsetParent) {
el = el.offsetParent;
top += el.offsetTop;
left += el.offsetLeft;
}
return (
top < (window.pageYOffset + window.innerHeight) &&
left < (window.pageXOffset + window.innerWidth) &&
(top + height) > window.pageYOffset &&
(left + width) > window.pageXOffset
);
}
function debounce(func, delay) {
let debounceTimer;
return function () {
const context = this;
const args = arguments;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => func.apply(context, args), delay);
};
}
function videoPlaying(el) {
return !!(el.currentTime > 0 && !el.paused && !el.ended && el.readyState > 2);
}
function videoLoaded(el) {
return el.readyState === 4
}
let renderEventListener = null
let previewExists = {}
export default {
components: { EqWindowSimple, EqWindow, PageHeader },
data() {
return {
loaded: false,
previews: [],
filteredPreviews: [],
search: "",
previewBaseUrl: App.ASSET_PLAYER_ANIMATION_CLIPS,
routeWatcher: null,
}
},
created() {
this.init()
},
methods: {
init() {
if (!this.$route.query.q) {
this.search = ""
this.filteredRaces = []
}
// create route watcher
this.routeWatcher = this.$watch('$route.query', () => {
this.search = this.$route.query.q
this.previewAnimSearch();
});
this.render()
this.previewAnimSearch()
// render scroll listener
if (Listeners.EmitterViewerRenderListener) {
window.removeEventListener("scroll", Listeners.EmitterViewerRenderListener)
}
Listeners.EmitterViewerRenderListener = debounce(handleRender, 100)
window.addEventListener("scroll", Listeners.EmitterViewerRenderListener);
},
render: function () {
// Preload model files
let modelFiles = [];
Previews[0].contents.forEach((row) => {
const pieces = row.name.split(/\//);
const fileName = pieces[pieces.length - 1].replace(".mp4", "");
const animationId = parseInt(fileName)
modelFiles.push(animationId)
previewExists[animationId] = 1
})
// Sort by preview animation number
modelFiles.sort(function (a, b) {
return a - b;
});
this.previews = modelFiles
this.loaded = true
setTimeout(() => {
handleRender()
}, 500);
},
triggerSearch: debounce(function () {
this.$router.push(
{
path: ROUTE.EMITTER_VIEWER,
query: {
q: this.search
}
}
).catch(err => err)
}, 1000),
previewAnimSearch: function () {
this.loaded = false
let filteredPreviews = []
// Sort by preview animation number
filteredPreviews.sort(function (a, b) {
return a - b;
});
if (filteredPreviews.length > 0) {
this.filteredPreviews = filteredPreviews
} else {
this.filteredPreviews = this.previews
}
this.loaded = true
setTimeout(() => {
handleRender()
}, 100);
}
},
activated() {
this.init()
},
deactivated() {
if (Listeners.EmitterViewerRenderListener) {
console.log("Removing listener")
window.removeEventListener("scroll", Listeners.EmitterViewerRenderListener, true)
Listeners.EmitterViewerRenderListener = null
}
// remove route watcher
this.routeWatcher()
},
beforeDestroy() {
if (Listeners.EmitterViewerRenderListener) {
console.log("Removing listener2")
window.removeEventListener("scroll", Listeners.EmitterViewerRenderListener, true)
Listeners.EmitterViewerRenderListener = null
}
},
props: {
isComponent: { // here for now because this viewer wasn't built as a component in mind
default: false,
required: false,
type: Boolean,
},
}
}
</script>

<style>
.player-anim-preview {
height: 270px;
width: 480px;
border-radius: 10px;
margin: 3px;
}
.overlay {
position: absolute;
bottom: 2px;
left: 9px;
}
</style>
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "spire",
"version": "1.4.0",
"version": "1.5.0",
"repository": {
"type": "git",
"url": "https://github.com/Akkadius/spire.git"
Expand Down

0 comments on commit ea83811

Please sign in to comment.