Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Viewer] Implement Player Animation Viewer #12

Merged
merged 2 commits into from
Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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