import {Point} from "../Misc.js";
/**
* Parent Drawable class, handles shared attributes and actions of drawables.
*
* @property {string} name Name of the drawable, used for searching for it within element.
* @property {number} level Depth of drawable within element. Higher number will be on top.
* @property {number} dx Deviation on X axis in pixels from center of parent Element.
* @property {number} dy Deviation on Y axis in pixels from center of parent Element.
* @property {number} width Width of the drawable in pixels. Used for rectangles and images.
* @property {number} height Height of the drawable in pixels. Used for rectangles and images.
* @property {number} rotation Rotation of the drawable. Measured in radians.
* @property {boolean} visible Visibility of drawable. Skips drawing on false.
* @property {number} hScale Scale of drawable on horizontal axis. 1 is default, -1 is mirrored.
* @property {number} vScale Scale of drawable on vertical axis. 1 is default, -1 is mirrored.
*/
class GameDrawable {
#name = undefined
get name() {
return this.#name
}
#level = 0
set level(newLevel) {
if (newLevel === undefined) {
this.#level = 0
return
}
if (isNaN(newLevel)) {
throw new TypeError("Incorrect type of level, it has to be a number!")
}
this.#level = Number(newLevel)
}
get level() {
return this.#level
}
#dx = 0
set dx(newDX) {
if (newDX === undefined) {
this.#dx = 0
return
}
if (isNaN(newDX)) {
throw new TypeError("Incorrect type of dx, it has to be a number!")
}
this.#dx = Number(newDX)
}
get dx() {
return this.#dx
}
#dy = 0
set dy(newDY) {
if (newDY === undefined) {
this.#dy = 0
return
}
if (isNaN(newDY)) {
throw new TypeError("Incorrect type of dy, it has to be a number!")
}
this.#dy = Number(newDY)
}
get dy() {return this.#dy}
#width = undefined
set width(newWidth) {
if (newWidth === undefined) {
this.#width = undefined
return
}
if (isNaN(newWidth)) {
throw new TypeError("Incorrect type of width, it has to be a number!")
}
this.#width = Number(newWidth)
}
get width() {return this.#width}
#height = undefined
set height(newHeight) {
if (newHeight === undefined) {
this.#height = undefined
return
}
if (isNaN(newHeight)) {
throw new TypeError("Incorrect type of height, it has to be a number!")
}
this.#height = Number(newHeight)
}
get height() {return this.#height}
#rotation = 0
set rotation(newRotation) {
if (newRotation === undefined) {
this.#rotation = 0
return
}
if (isNaN(newRotation)) {
throw new TypeError("Incorrect type of rotation, it has to be a number!")
}
this.#rotation = Number(newRotation)
}
get rotation() {return this.#rotation}
#visible = true
set visible(newVisible) {
if (newVisible === undefined) {
this.#visible = true
return
}
if (newVisible instanceof Boolean) {
throw new TypeError("Incorrect type for visible, it has to be a boolean!")
}
this.#visible = newVisible
}
get visible() {return this.#visible}
#hScale = 1
set hScale(newScale) {
if (newScale === undefined) {
this.#hScale = 1
return
}
if (isNaN(newScale)) {
throw new TypeError("Incorrect type of hScale, it has to be a number!")
}
this.#hScale = Number(newScale)
}
get hScale() {return this.#hScale}
#vScale = 1
set vScale(newScale) {
if (newScale === undefined) {
this.#vScale = 1
return
}
if (isNaN(newScale)) {
throw new TypeError("Incorrect type of vScale, it has to be a number!")
}
this.#vScale = Number(newScale)
}
get vScale() {return this.#vScale}
/**
* @param {Object} attrs Attributes of new Drawable.
*/
constructor(attrs={}) {
this.#name = attrs.name;
this.level = attrs.level
this.dx = attrs.dx
this.dy = attrs.dy
this.width = attrs.width;
this.height = attrs.height;
this.rotation = attrs.rotation
this.visible = attrs.visible
this.hScale = attrs.hScale
this.vScale = attrs.vScale
}
/**
* Draws the object on the supplied context
* @param {CanvasRenderingContext2D} ctx Rendering context on which the method draws
*/
drawFunction(ctx) {
console.error("Calling drawFunction in class GameDrawable. Use a subclass!")
}
/**
* Transforms input context and calls draw function of passed drawable.
* @param {CanvasRenderingContext2D} ctx Rendering context.
*/
draw(ctx) {
ctx.save()
ctx.transform(this.hScale,0,0,this.vScale,this.dx,this.dy);
ctx.rotate(this.rotation)
this.drawFunction(ctx)
ctx.restore()
}
/**
* Returns true when mouse is inside drawable.
* @param {Point} mouse Mouse position on canvas.
* @param {CanvasRenderingContext2D} tempContext Hidden rendering context to check pixel state.
* @returns {boolean} True when mouse is inside drawable, false otherwise.
*/
isInside(mouse, tempContext) {
// draws the object to invisible context
this.draw(tempContext)
// get the pixel array
const imageData = tempContext.getImageData(0, 0,tempContext.canvas.width,tempContext.canvas.height);
// get the index of clicked pixel in pixel array
const pixelIndex = Math.floor(mouse.x) * 4 + Math.floor(mouse.y) * 4 * Math.floor(tempContext.canvas.width);
// get alpha at clicked pixel
const alpha=imageData.data[pixelIndex+3];
// clicked pixel is not empty
return alpha !== 0
}
/**
* Returns object of attributes of current instance.
* @returns {Object} Attribute object.
*/
getAttrs() {
return {
name : this.name,
level : this.level,
dx : this.dx,
dy : this.dy,
width : this.width,
height : this.height,
rotation : this.rotation,
hScale : this.hScale,
vScale : this.vScale,
visible : this.visible,
}
}
/**
* Returns copy of current instance.
* @param {string} newName Name of the newly created instance. Names have to be unique.
* @returns {GameDrawable} New instance of Drawable with the same attributes.
*/
copy(newName) {
const attrs = this.getAttrs()
if (newName === undefined) {
attrs.name = (attrs.name === undefined) ? undefined : attrs.name + "_copy"
} else {
attrs.name = newName
}
return new GameDrawable(attrs)
}
}
/**
* Loads image from url.
* @param {string} imageUrl Source url.
* @returns {Promise<Image>} Image object.
* @function
*/
async function loadImage(imageUrl) {
let img;
const imageLoadPromise = new Promise(resolve => {
img = new Image();
img.onload = resolve;
img.src = imageUrl;
});
await imageLoadPromise;
return img;
}
export { GameDrawable, loadImage }