Skip to content
Snippets Groups Projects
01-canvasmodel-utils.js 4.67 KiB
Newer Older
/**
 * (Low-level API)
 * Prepare a layer to be rendered.
 *
 * See {@link CompositeLayerSpec} definition for details on options.
 *
 * Arguments are processed depending on their type:
Jimmy's avatar
Jimmy committed
 * String argument is `src` option (image source)
 * Number argument is `z` option (z-index)
 * Object argument is full or partial  CompositeLayerSpec object
 * Leftmost arguments have most priority.
Jimmy's avatar
Jimmy committed
 *
 * @example
 * <<run canvaslayer(10, 'img.png', {blendMode:'hard-light', blend:'#00ff00'});>>
 * <<run canvaslayer({z:10, src:'img.png'}, {blendMode:'hard-light', blend:'#00ff00'});>>
 */
function canvaslayer() {
Jimmy's avatar
Jimmy committed
	const layers = T.CanvasLayers;
	if (!layers) throw new Error("'canvaslayer()' without 'canvasstart'");
Jimmy's avatar
Jimmy committed
	let theOptions = {};
	for (let i = arguments.length - 1; i >= 0; i--) {
		const arg = arguments[i];
		switch (typeof arg) {
Jimmy's avatar
Jimmy committed
			case "object":
				theOptions = Object.assign(theOptions, arg);
				break;
Jimmy's avatar
Jimmy committed
			case "string":
				theOptions.src = arg;
				break;
Jimmy's avatar
Jimmy committed
			case "number":
				theOptions.z = arg;
				break;
			default:
Jimmy's avatar
Jimmy committed
				throw new Error("Invalid canvaslayer() argument " + i + ": " + typeof arg);
Jimmy's avatar
Jimmy committed
	if (typeof theOptions.src !== "string") {
		console.error(arguments);
Jimmy's avatar
Jimmy committed
		throw new Error("canvaslayer() options missing 'src'");
	}
	layers.push(theOptions);
}
window.canvaslayer = canvaslayer;

// use 2x2 pixels in generated images
Renderer.pixelSize = 2;

Renderer.Stats = {
	trace: false,
	traceAnim: false,
	lastLoadTime: 0,
	lastRenderTime: 0,

	logmsgLoad: new ObservableValue(""),
	logmsgRender: new ObservableValue(""),
	logmsgAnimate: new ObservableValue(""),

	nlayers: 0,
Jimmy's avatar
Jimmy committed
	ncached: 0,
Jimmys's avatar
Jimmys committed
/**
 * @type {Renderer.RendererListener}
 */
Renderer.defaultListener = {
Jimmy's avatar
Jimmy committed
	error(error) {
		// strip source data
Jimmy's avatar
Jimmy committed
		const msg = (error.stack || error.message || "" + error).replace(/\(?[^( )]*:\d+:\d+\)?/gm, "").replace(/\(?eval at [\w.]+/gm, "");
		Errors.report(msg);
	},
Jimmy's avatar
Jimmy committed
	composeLayers(layers) {
		Renderer.Stats.loadErrors = 0;
		if (Renderer.Stats.trace) {
Jimmy's avatar
Jimmy committed
			console.log(DOL.Perflog.millitime().toFixed(3), "Composing " + layers.length + " layers...");
Jimmy's avatar
Jimmy committed
	processingStep(layer, processing, canvas, dt) {
		DOL.Perflog.logWidgetTime("_render:" + processing, dt);
Jimmy's avatar
Jimmy committed
	loadError(layer, src) {
		// logged to console by Renderer itself
		Renderer.Stats.loadErrors++;
Jimmy's avatar
Jimmy committed
		Errors.report("Failed to load image " + src + " for layer " + layer);
Jimmy's avatar
Jimmy committed
	loadingDone(time, layersLoaded) {
		Renderer.Stats.lastLoadTime = time;
Jimmy's avatar
Jimmy committed
		let msg = "Loaded " + layersLoaded + " images in " + time.toFixed(3) + " ms";
		if (Renderer.Stats.loadErrors > 0) msg += " (" + Renderer.Stats.loadErrors + " failed)";
		Renderer.Stats.logmsgLoad.value = msg;
		if (Renderer.Stats.trace) {
			console.log(DOL.Perflog.millitime().toFixed(3), msg);
		}
	},
Jimmy's avatar
Jimmy committed
	beforeRender(layers) {
		Renderer.Stats.nlayers = layers.length;
		Renderer.Stats.ncached = 0;
	},
Jimmy's avatar
Jimmy committed
	layerCacheHit(layer) {
		Renderer.Stats.ncached++;
	},
Jimmy's avatar
Jimmy committed
	renderingDone(time) {
		Renderer.Stats.lastRenderTime = time;
Jimmy's avatar
Jimmy committed
		const msg = "Rendered " + Renderer.Stats.nlayers + " layers" + " (" + Renderer.Stats.ncached + " cached)" + " in " + time.toFixed(3) + " ms";
		if (Renderer.Stats.trace) {
Jimmy's avatar
Jimmy committed
			console.log(DOL.Perflog.millitime().toFixed(3), msg);
		}
		Renderer.Stats.logmsgRender.value = msg;
	},
Jimmy's avatar
Jimmy committed
	keyframe(animation, keyframeIndex, keyframe) {
		if (Renderer.Stats.traceAnim) {
Jimmy's avatar
Jimmy committed
			console.log(
				DOL.Perflog.millitime().toFixed(3),
				"animation",
				animation,
				"keyframe",
				keyframeIndex,
				"frame",
				keyframe.frame,
				"duration",
				keyframe.duration
			);
Jimmy's avatar
Jimmy committed
	keyframeRender(spec, cacheHit, cacheRenderTime) {
		if (Renderer.Stats.traceAnim) {
Jimmy's avatar
Jimmy committed
			console.log(
				DOL.Perflog.millitime().toFixed(3),
				"KeyframeRender",
				spec,
				cacheHit ? "cache hit, render time " + cacheRenderTime.toFixed(3) + " ms" : "cache miss"
			);
		}
		if (cacheHit && Renderer.Stats.logmsgAnimate) {
Jimmy's avatar
Jimmy committed
			Renderer.Stats.logmsgAnimate.value = "Cached keyframe rendered in " + cacheRenderTime.toFixed(3) + " ms";
Jimmy's avatar
Jimmy committed
	animationStop() {
		if (Renderer.Stats.traceAnim) {
			console.log(DOL.Perflog.millitime().toFixed(3), "Animation stopped");
		}
Jimmy's avatar
Jimmy committed
	},
};
xao's avatar
xao committed

xao's avatar
xao committed
function refreshCanvas(model) {
xao's avatar
xao committed
	requestAnimationFrame(() => {
		const canvasModel = Renderer.locateModel(model, "sidebar");
		if (canvasModel.canvas) {
			Renderer.invalidateLayerCaches(canvasModel.layerList);
			canvasModel.redraw();
		}
	});
xao's avatar
xao committed
}

function refreshModels(e, overlay) {
	if (overlay === "options") {
		refreshCanvas("lighting");
xao's avatar
xao committed
	}
}

/* Events */
Purity's avatar
Purity committed
$(document).on(":passagestart", () => {
	if (State.current !== State.top) {
		Skin.recache();
	}
});
$(document).on(":onloadsave", () => {
	Skin.recache();
	refreshCanvas("lighting");
});
$(document).on(":enginerestart", () => {
	Skin.recache();
});
xao's avatar
xao committed
$(document).on(":oncloseoverlay", refreshModels);