Skip to content

Commit

Permalink
Spritesheet player for sequencer database viewer
Browse files Browse the repository at this point in the history
  • Loading branch information
Codas committed Sep 18, 2024
1 parent 787f0fc commit b8ee848
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/canvas-effects/sequencer-sprite-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class VideoSpritesheetAsset extends Asset {
this.#register();
}
destroy() {
SequencerFileCache.unloadSpritesheet(this.filepath);
return SequencerFileCache.unloadSpritesheet(this.filepath);
}
#register() {
SequencerFileCache.registerSpritesheet(this.filepath, this.spritesheet);
Expand Down
32 changes: 22 additions & 10 deletions src/formapplications/database/DatabaseStore.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import SequencerDatabase from "../../modules/sequencer-database.js";
import { SequencerFileBase } from "../../modules/sequencer-file.js";
import { get, writable } from "svelte/store";
import CONSTANTS from "../../constants.js";
import * as lib from "../../lib/lib.js";
import { writable, get } from "svelte/store";
import SequencerDatabase from "../../modules/sequencer-database.js";
import { SequencerFileBase } from "../../modules/sequencer-file.js";
import TreeViewEntry from "./TreeViewEntry.svelte";
import TreeViewSeparator from "./TreeViewSeparator.svelte";
import { cleanupSpritesheet,playSpritesheet } from "./renderSpritesheet.js";

let lastFile = false;

Expand All @@ -30,10 +31,11 @@ function getFileData(entryText) {
lowerCaseEntry.endsWith("ogg") ||
lowerCaseEntry.endsWith("mp3") ||
lowerCaseEntry.endsWith("wav");
const isImage = !lowerCaseEntry.endsWith("webm") && !isAudio;
const isVideo = !isAudio && !isImage;
const isSpritesheet = lowerCaseEntry.endsWith("json");
const isImage = !lowerCaseEntry.endsWith("webm") && !isAudio && !isSpritesheet;
const isVideo = !isAudio && !isImage && !isSpritesheet;
const icon = previewFile
? isVideo
? isVideo || isSpritesheet
? "fa-film"
: isAudio
? "fa-volume-high"
Expand All @@ -42,6 +44,8 @@ function getFileData(entryText) {
const title = previewFile
? isVideo
? "Animated WebM"
: isSpritesheet
? "Spritesheet"
: isAudio
? "Audio"
: "Image"
Expand All @@ -54,6 +58,7 @@ function getFileData(entryText) {
title,
isAudio,
isImage,
isSpritesheet,
isVideo,
};
}
Expand Down Expand Up @@ -96,11 +101,11 @@ function copyPath(dbPath, getFilepath, quotes = false) {
document.execCommand("copy");
}

function playFile(entry) {
const { file, isAudio, isImage, isVideo } = getFileData(entry);

async function playFile(entry) {
await cleanupSpritesheet()
const { file, isAudio, isImage, isVideo, isSpritesheet } = getFileData(entry);
databaseStore.elements.audio.classList.toggle("hidden", !isAudio);
databaseStore.elements.image.classList.toggle("hidden", !isImage);
databaseStore.elements.image.classList.toggle("hidden", !isImage && !isSpritesheet);
databaseStore.elements.player.classList.toggle("hidden", !isVideo);

if (isImage) {
Expand All @@ -112,6 +117,11 @@ function playFile(entry) {
return;
}

if(isSpritesheet) {
playSpritesheet(file, databaseStore);
return;
}

const element = isAudio
? databaseStore.elements.audio
: databaseStore.elements.player;
Expand All @@ -136,6 +146,7 @@ function playFile(entry) {
element.src = file;
}


const treeStore = writable({});
const visibleTreeStore = writable([]);
let flattenedEntries = [];
Expand All @@ -162,6 +173,7 @@ const databaseStore = {
cleanSearchStore: cleanSearchStore,
searchRegex: searchRegexStore,
elements: {},
cleanupSpritesheet() { cleanupSpritesheet(true) },
copyPath,
playFile,
openTreePath,
Expand Down
3 changes: 2 additions & 1 deletion src/formapplications/database/database-shell.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { ApplicationShell } from "#runtime/svelte/component/core";
import { localize } from '#runtime/svelte/helper';
import { getContext } from "svelte";
import { getContext, onDestroy } from "svelte";
import DatabaseEntry from "./DatabaseEntry.svelte";
import VirtualScroll from "svelte-virtual-scroll-list"
Expand Down Expand Up @@ -62,6 +62,7 @@
});
}
onDestroy(() => databaseStore.cleanupSpritesheet());
</script>

<svelte:options accessors={true}/>
Expand Down
100 changes: 100 additions & 0 deletions src/formapplications/database/renderSpritesheet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import SequencerFileCache from "../../modules/sequencer-file-cache";

const viewSize = 320;
let animatedSprite;
let offscreenApp;
let canvas;
let offscreenCanvas;
let oldImageSrcs = new Map();

function initOffscreenApp(databaseStore) {
if (offscreenApp) {
return;
}
canvas = document.createElement("canvas");
canvas.style.display = "none";
offscreenCanvas = canvas.transferControlToOffscreen();
const backgroundColor =
offscreenApp = new PIXI.Application({ autoStart: false, view: offscreenCanvas, backgroundColor: 0x999999, sharedTicker: false });
offscreenApp.ticker.stop();
// worker thread would be nice, but then we would need the webworker version of pixi.js too...
document.body.appendChild(canvas);
// workaround to fix errors when destroying the offscreen app
offscreenCanvas.style = {}
offscreenCanvas.width = viewSize;
offscreenCanvas.height = viewSize;
}

export async function playSpritesheet(file, databaseStore) {
initOffscreenApp(databaseStore);
const sheet = await SequencerFileCache.loadFile(file);
if (!sheet) {
return;
}
SequencerFileCache.registerSpritesheet(file, sheet);
const frames = Object.values(sheet.animations)?.[0];
if (!frames) {
return;
}
const framerate = sheet.data?.meta?.framerate ?? 30;
const frametime = (1 / framerate) * 1000;
databaseStore.metadata.set({
type: "Spritesheet",
duration: Math.round(frametime * frames.length) + "ms",
});
if (!animatedSprite) {
animatedSprite = new PIXI.AnimatedSprite(frames, true);
animatedSprite.anchor.set(0.5);
animatedSprite.x = viewSize / 2;
animatedSprite.y = viewSize / 2;
offscreenApp.stage.addChild(animatedSprite);
}
let queuedFrame;
let renderedFrame;
animatedSprite.onFrameChange = async (frame) => {
// rendering operation is async. If we're currently still rendering
// the previous frame, don't even try to render the current one
if (queuedFrame !== renderedFrame) {
return;
}
let curerntImageSrc = oldImageSrcs.get(frame);
if (!curerntImageSrc) {
queuedFrame = frame;
offscreenApp.render()
const imageBlob = await offscreenCanvas.convertToBlob();
curerntImageSrc = URL.createObjectURL(imageBlob);
oldImageSrcs.set(frame, curerntImageSrc);
renderedFrame = frame;
}
databaseStore.elements.image.src = curerntImageSrc;
};
animatedSprite.textures = frames;
animatedSprite.scale.set(viewSize / Math.max(frames[0].width, frames[0].height));
animatedSprite.file = file;
animatedSprite.play();
}

export async function cleanupSpritesheet(destroy = false) {
if (!animatedSprite) {
return;
}
animatedSprite.stop();
animatedSprite.onFrameChange = null;
if (animatedSprite.file) {
await SequencerFileCache.unloadSpritesheet(animatedSprite.file);
animatedSprite.file = null;
}

oldImageSrcs.values().forEach((objectUrl) => {
URL.revokeObjectURL(objectUrl);
});
oldImageSrcs.clear();
if (destroy) {
animatedSprite.destroy();
animatedSprite = null;
offscreenApp.destroy();
canvas.remove()
offscreenCanvas = null;
offscreenApp = undefined;
}
}
7 changes: 4 additions & 3 deletions src/lib/filters/vision-sampler-shader.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ export default class VisionSamplerShader extends BaseSamplerShader {
precision ${PIXI.settings.PRECISION_VERTEX} float;
uniform vec2 screenDimensions;
uniform mat3 projectionMatrix;
uniform mat3 translationMatrix;
uniform vec4 tint;
in vec2 aVertexPosition;
in vec2 aTextureCoord;
Expand All @@ -70,9 +73,7 @@ export default class VisionSamplerShader extends BaseSamplerShader {
in float aTextureId;
in float aTilingEnabled;
in float aVisionMaskingEnabled;
uniform mat3 projectionMatrix;
uniform mat3 translationMatrix;
uniform vec4 tint;
out vec2 vTextureCoord;
out vec2 vVisionCoord;
flat out vec4 vColor;
Expand Down
7 changes: 4 additions & 3 deletions src/modules/sequencer-file-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,11 @@ const SequencerFileCache = {
}
},

unloadSpritesheet(inSrc) {
async unloadSpritesheet(inSrc) {
const existingSheetRef = this._spritesheets.get(inSrc)
if (!existingSheetRef) {
console.error('trying to unlaod spritesheet that was not loaded:', inSrc)
return;
}
existingSheetRef[1] -= 1
if (existingSheetRef[1] > 0) {
Expand All @@ -104,11 +105,11 @@ const SequencerFileCache = {
const sheet = existingSheetRef[0]
const relatedPacks = sheet.data?.meta?.related_multi_packs ?? []
const relatedSheets = sheet.linkedSheets
const packsSize = Math.max(relatedPacks.length, relatedSheets.length)
// const packsSize = Math.max(relatedPacks.length, relatedSheets.length)
// clean up related sheets starting with the last (leaf)

const cacheKeys = [get_sheet_image_url(inSrc, sheet), foundry.utils.getRoute(inSrc)]
PIXI.Assets.unload(cacheKeys.filter(src => !!src))
await PIXI.Assets.unload(cacheKeys.filter(src => !!src))
}
};

Expand Down

0 comments on commit b8ee848

Please sign in to comment.