/**
* Only do rendering here, keep all drawing functions and counter mutators in ui.pm
* @fileoverview Presentation Manager module abstracts functions common to all games.
* @author Robert Laing
* @module pm
*/
/**
* The destination portion with only a string key referencing the binary (ie "nontransferable") data stored separately in imagesDict.
* @typedef {Object} module:pm.renderObject
* @property {string} name - key to imagesDict, creating a "flyweight" pattern to avoid duplicating image blobs, also allows structuredClone
* @property {Array} [base] - base in state corresponding to counter
* @property {pixels} x1 - sprite's current center X, used by ctx.translate
* @property {pixels} y1 - sprite's current center Y, used by ctx.translate
* @property {pixels} sx - Best draw() looks for the source corner here in case it's a sprite
* @property {pixels} sy - Best draw() looks for the source corner here in case it's a sprite
* @property {pixels} sWidth - Best draw() looks for the source corner here in case it's a sprite
* @property {pixels} sHeight - Best draw() looks for the source corner here in case it's a sprite
* @property {radians} angle1 - 0 for stationary drawn images or Math.PI/2 for sprites, Math.atan2(y2 - y1, x2 - x1) for moving images
* @property {pixels} [x2] - sprite's destination center X, same as x1 if not moving
* @property {pixels} [y2] - sprite's destination center Y
* @property {radians} [angle2] - sprite's destination direction, same as angle1 if it's not rotating
* @property {pixels} [velocity] - 0.0 if the sprite is stationary
* @property {pixels} [deltaX] - added to x1 each tick, Math.cos(angle1 + Math.PI/2) * velocity (the 90 degree addition is to remove the sprite facing up)
* @property {pixels} [deltaY] - added to y1 each tick, Math.sin(angle1 + Math.PI/2) * velocity
* @property {degrees} [deltaAngle] -- added to angle1 each tick, controls how fast sprites rotate
* @property {pixels} width - needed by ctx.drawImage
* @property {pixels} height - needed by ctx.drawImage
* @property {boolean} live - if false, gets removed from sprites array
* @property {string} [status] - per game lables such as "idle", "selectable", "selected", "moving", "attacking", "defending", "killed" used to access relevant update function.
*/
/**
* The source portion and binary (ie "nontransferable") data of an item to be drawn in a canvas.
* @typedef {Object} module:pm.imageObject
* @property {Image} image - loaded from a file or offscreen canvas drawing
* @property {boolean} loaded - a flag to avoid ctx.drawImage crashes
* @property {pixels} sx - left hand side of source image, usually 0 unless from sprite sheet
* @property {pixels} sy - top of source image, usually 0 unless from sprite sheet
* @property {pixels} sWidth - source width
* @property {pixels} sHeight - source height
*/
/**
* A flyweight pattern to hold blobs of images to be used any number of times by renderObjects
* @member {{name: imageObject}} module:pm.imagesDict - Accessed by clients via upsert_imagesDict(name, imageObject)
*/
const imagesDict = {};
/**
* @member {{name: renderObject}} module:pm.renderDict - Accessed by clients via upsert_renderDict(name, renderObject)
*/
const renderDict = {};
/**
* Exported function to add or replace an imageObject in imagesDict
* @function module:pm.imagesDict
* @param {string} key - not necessarily the same as renderObject.name, but can be.
* @param {imageObject} value - An object with the relevant keys for the given item to be rendered.
*/
function upsert_imagesDict(key, value) {
imagesDict[key] = value;
}
/**
* Exported function to add or replace a renderObject in renderDict
* @function module:pm.renderDict
* @param {string} key - not necessarily the same as renderObject.name, but can be.
* @param {renderObject} value - An object with the relevant keys for the given item to be rendered.
*/
function upsert_renderDict(key, value) {
renderDict[key] = value;
}
function mutateRenderDict(key, property, value) {
renderDict[key][property] = value;
}
/**
* @function module:pm.render
* @param {string[]} renderArr
*/
function render(renderArr) {
renderArr.forEach(function(name) {
draw(window.ctx, renderDict[name]);
});
}
/**
* Moved sx, sy, sWidth, sHeight to renderObject, so no longer reference via imagesDict[renderObject.name] to allow sprites.
* @function module:pm.draw
* @param {CanvasRenderingContext2D} ctx - Either for an OffscreenCanvas or browser canvas.
* @param {renderObject} renderObject - Note the renderObject looks up the source image via the name string.
*/
function draw(ctx, renderObject) {
if (renderObject && imagesDict[renderObject.name] && imagesDict[renderObject.name].loaded) {
try {
ctx.save();
ctx.translate(renderObject.x1, renderObject.y1);
ctx.rotate(renderObject.angle1);
ctx.drawImage(
imagesDict[renderObject.name].image,
renderObject.sx, renderObject.sy,
renderObject.sWidth, renderObject.sHeight,
-renderObject.width/2, -renderObject.height/2, renderObject.width, renderObject.height);
ctx.restore();
} catch (error) {
window.container.textContent = error;
}
}
}
/**
* @function module:pm.resize
*/
function resize(scale) {
Object.values(renderDict).forEach(function(renderObject) {
if (renderObject) {
renderObject.x1 *= scale;
renderObject.y1 *= scale;
renderObject.width *= scale;
renderObject.height *= scale;
}
});
}
export default Object.freeze({imagesDict, renderDict, upsert_imagesDict, upsert_renderDict, mutateRenderDict, render, draw, resize});