574 lines
12 KiB
JavaScript
574 lines
12 KiB
JavaScript
|
export class Renderer {
|
||
|
constructor(canvas, game) {
|
||
|
this.game = game;
|
||
|
game.renderer = this;
|
||
|
|
||
|
this.canvas = canvas;
|
||
|
this.ctx = this.canvas.getContext('2d');
|
||
|
|
||
|
this.width = canvas.width;
|
||
|
this.height = canvas.height;
|
||
|
|
||
|
this.anchorOption = {
|
||
|
hor: 'left',
|
||
|
ver: 'top'
|
||
|
};
|
||
|
|
||
|
this.virtualWidth = 100;
|
||
|
this.virtualHeight = 100;
|
||
|
}
|
||
|
|
||
|
setSize(width, height) {
|
||
|
this.width = width;
|
||
|
this.height = height;
|
||
|
|
||
|
this.canvas.width = width;
|
||
|
this.canvas.height = height;
|
||
|
}
|
||
|
|
||
|
draw() {
|
||
|
this.ctx.clearRect(0, 0, this.width, this.height);
|
||
|
|
||
|
this.game.draw(this);
|
||
|
|
||
|
window.requestAnimationFrame(() => this.draw());
|
||
|
}
|
||
|
|
||
|
begin() {
|
||
|
const drawing = new Drawing(this);
|
||
|
drawing.anchor(this.anchorOption.hor, this.anchorOption.ver);
|
||
|
return drawing;
|
||
|
}
|
||
|
|
||
|
push() {
|
||
|
this.ctx.save();
|
||
|
}
|
||
|
|
||
|
pop() {
|
||
|
this.ctx.restore();
|
||
|
}
|
||
|
|
||
|
anchor(hor, ver) {
|
||
|
let x = 0;
|
||
|
let y = 0;
|
||
|
switch (hor) {
|
||
|
case 'center':
|
||
|
x = this.width / 2;
|
||
|
break;
|
||
|
case 'right':
|
||
|
x = this.width;
|
||
|
break;
|
||
|
}
|
||
|
switch (ver) {
|
||
|
case 'center':
|
||
|
y = this.height / 2;
|
||
|
break;
|
||
|
case 'bottom':
|
||
|
y = this.height;
|
||
|
}
|
||
|
|
||
|
this.translate(x, y);
|
||
|
|
||
|
this.anchorOption = {
|
||
|
hor,
|
||
|
ver
|
||
|
};
|
||
|
}
|
||
|
|
||
|
rotate(degrees) {
|
||
|
this.ctx.rotate(degrees * Math.PI / 180);
|
||
|
}
|
||
|
|
||
|
translate(x, y) {
|
||
|
this.ctx.translate(x, y);
|
||
|
}
|
||
|
|
||
|
scale(x, y) {
|
||
|
this.ctx.scale(x, y);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class Drawing {
|
||
|
constructor(renderer) {
|
||
|
this.renderer = renderer;
|
||
|
this.ctx = renderer.ctx;
|
||
|
|
||
|
this.ctx.save();
|
||
|
|
||
|
this.anchorPoint = {
|
||
|
x: 0,
|
||
|
y: 0,
|
||
|
};
|
||
|
this.offset = {
|
||
|
x: 0,
|
||
|
y: 0,
|
||
|
};
|
||
|
|
||
|
this.translation = {
|
||
|
x: 0,
|
||
|
y: 0,
|
||
|
};
|
||
|
this.rotation = 0;
|
||
|
|
||
|
this.fillColor = undefined;
|
||
|
this.strokeColor = undefined;
|
||
|
this.lineWidth = 1;
|
||
|
this.lineCap = 'butt';
|
||
|
this.lineJoin = 'miter';
|
||
|
|
||
|
this.type = undefined;
|
||
|
this.args = {};
|
||
|
|
||
|
this.textOptions = {
|
||
|
font: 'sans-serif',
|
||
|
fontSize: 16,
|
||
|
alignment: 'left',
|
||
|
baseline: 'alphabetic',
|
||
|
};
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Shapes
|
||
|
*/
|
||
|
|
||
|
|
||
|
arc(x, y, radius, startAngle, endAngle) {
|
||
|
this.type = 'arc';
|
||
|
this.args = {
|
||
|
x,
|
||
|
y,
|
||
|
radius,
|
||
|
startAngle,
|
||
|
endAngle
|
||
|
};
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
circle(x, y, radius) {
|
||
|
this.type = 'circle';
|
||
|
this.args = {
|
||
|
x,
|
||
|
y,
|
||
|
radius
|
||
|
};
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
rect(x, y, width, height) {
|
||
|
this.type = 'rect';
|
||
|
this.args = {
|
||
|
x,
|
||
|
y,
|
||
|
width,
|
||
|
height
|
||
|
};
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
roundedRect(x, y, width, height, roundness) {
|
||
|
this.type = 'roundedRect';
|
||
|
this.args = {
|
||
|
x,
|
||
|
y,
|
||
|
width,
|
||
|
height,
|
||
|
roundness
|
||
|
};
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
triangle(x, y, width, height) {
|
||
|
this.type = 'triangle';
|
||
|
this.args = {
|
||
|
x1: x,
|
||
|
y1: y,
|
||
|
x2: x + width,
|
||
|
y2: y,
|
||
|
x3: x + width / 2,
|
||
|
y3: y + height
|
||
|
};
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Path
|
||
|
*/
|
||
|
|
||
|
vertex(x, y) {
|
||
|
this.type = 'line';
|
||
|
if (!this.args.vertices) {
|
||
|
this.args.vertices = [];
|
||
|
}
|
||
|
this.args.vertices.push([x, y]);
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
finish() {
|
||
|
this.args.close = true;
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Text
|
||
|
*/
|
||
|
|
||
|
|
||
|
text(string, x = 0, y = 0) {
|
||
|
this.type = 'text';
|
||
|
this.args = {
|
||
|
x,
|
||
|
y,
|
||
|
string
|
||
|
};
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set text alignment
|
||
|
* @param {'start'|'end'|'left'|'center'|'right'} alignment
|
||
|
* @returns {this}
|
||
|
*/
|
||
|
align(alignment) {
|
||
|
this.textOptions.alignment = alignment;
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets text baseline
|
||
|
* @param {'alphabetic'|'top'|'hanging'|'middle'|'ideographic'|'bottom'} base
|
||
|
* @return {Drawing}
|
||
|
*/
|
||
|
baseline(base) {
|
||
|
this.textOptions.baseline = base;
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
fontSize(fontSize) {
|
||
|
this.textOptions.fontSize = fontSize;
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Transformations
|
||
|
*/
|
||
|
|
||
|
|
||
|
anchor(hor, ver) {
|
||
|
switch (hor) {
|
||
|
case 'center':
|
||
|
this.anchorPoint.x = this.renderer.width / 2;
|
||
|
this.offset.x = -0.5;
|
||
|
break;
|
||
|
case 'right':
|
||
|
this.anchorPoint.x = this.renderer.width;
|
||
|
this.offset.x = -1;
|
||
|
break;
|
||
|
default:
|
||
|
this.anchorPoint.x = 0;
|
||
|
this.offset.x = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
switch (ver) {
|
||
|
case 'center':
|
||
|
this.anchorPoint.y = this.renderer.height / 2;
|
||
|
this.offset.y = -0.5;
|
||
|
break;
|
||
|
case 'bottom':
|
||
|
this.anchorPoint.y = this.renderer.height;
|
||
|
this.offset.y = -1;
|
||
|
break;
|
||
|
default:
|
||
|
this.anchorPoint.y = 0;
|
||
|
this.offset.y = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
rotate(amount, mode = 'DEGREES') {
|
||
|
if (mode === 'DEGREES') {
|
||
|
amount *= 180;
|
||
|
}
|
||
|
this.rotation += amount * Math.PI;
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
translate(x, y) {
|
||
|
this.translation.x += x;
|
||
|
this.translation.y += y;
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Translate to center of rectangle
|
||
|
* @param {number} x x position
|
||
|
* @param {number} y y position
|
||
|
* @param {number} width width
|
||
|
* @param {number} height height
|
||
|
* @returns {this}
|
||
|
*/
|
||
|
center(x, y, width, height) {
|
||
|
this.translation.x += x + this.offset.x * width + width / 2;
|
||
|
this.translation.y += y + this.offset.y * height + height / 2;
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
Colors
|
||
|
*/
|
||
|
|
||
|
|
||
|
fill(...color) {
|
||
|
this.fillColor = parseColor('rgb', ...color);
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
stroke(...color) {
|
||
|
this.strokeColor = parseColor('rgb', ...color);
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets line width for strokes
|
||
|
* @param {number} width
|
||
|
*/
|
||
|
strokeWidth(width) {
|
||
|
this.lineWidth = width;
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets line cap for strokes
|
||
|
* @param {'butt'|'round'|'square'} style
|
||
|
*/
|
||
|
strokeCap(style) {
|
||
|
this.lineCap = style;
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
calcCoords(x, y, width, height) {
|
||
|
return [
|
||
|
x + this.offset.x * width,
|
||
|
y + this.offset.y * height,
|
||
|
];
|
||
|
}
|
||
|
|
||
|
close() {
|
||
|
if (this.fillColor) {
|
||
|
this.ctx.fillStyle = this.fillColor;
|
||
|
}
|
||
|
if (this.strokeColor) {
|
||
|
this.ctx.strokeStyle = this.strokeColor;
|
||
|
this.ctx.lineWidth = this.lineWidth;
|
||
|
this.ctx.lineCap = this.lineCap;
|
||
|
}
|
||
|
|
||
|
this.ctx.rotate(this.rotation);
|
||
|
this.ctx.translate(this.translation.x, this.translation.y);
|
||
|
|
||
|
switch (this.type) {
|
||
|
case 'arc':
|
||
|
drawArc(this.ctx, this.args, !!this.fillColor, !!this.strokeColor);
|
||
|
break;
|
||
|
case 'circle':
|
||
|
drawCircle(this.ctx, this.args, !!this.fillColor, !!this.strokeColor);
|
||
|
break;
|
||
|
case 'line':
|
||
|
drawShape(this.ctx, this.args.close, this.args.vertices, !!this.fillColor, !!this.strokeColor);
|
||
|
break;
|
||
|
case 'rect':
|
||
|
/*if (this.fillColor) {
|
||
|
this.ctx.fillRect(...this.args);
|
||
|
}
|
||
|
if (this.strokeColor) {
|
||
|
this.ctx.strokeRect(...this.args);
|
||
|
}*/
|
||
|
break;
|
||
|
case 'roundedRect':
|
||
|
this.args.x = this.calcCoords(this.args.x, 0, this.args.width, 0)[0];
|
||
|
this.args.y = this.calcCoords(0, this.args.y, 0, this.args.height)[1];
|
||
|
|
||
|
drawRoundedRect(this.ctx, this.args, !!this.fillColor, !!this.strokeColor);
|
||
|
break;
|
||
|
case 'shape':
|
||
|
console.log('test');
|
||
|
drawShape(this.ctx, true, this.args, !!this.fillColor, !!this.strokeColor);
|
||
|
break;
|
||
|
case 'text':
|
||
|
this.ctx.font = this.textOptions.fontSize + 'px ' + this.textOptions.font;
|
||
|
this.args.x = this.calcCoords(this.args.x, 0, 0)[0];
|
||
|
this.args.y = this.calcCoords(0, this.args.y, 0, 0)[1];
|
||
|
|
||
|
drawText(this.ctx, this.args, !!this.fillColor, !!this.strokeColor, this.textOptions);
|
||
|
break;
|
||
|
case 'triangle':
|
||
|
drawTriangle(this.ctx, this.args, !!this.fillColor, !!this.strokeColor);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
this.ctx.restore();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function parseColor(colorMode, ...color) {
|
||
|
if (color.length === 0) {
|
||
|
return '#000000';
|
||
|
}
|
||
|
|
||
|
if (color.length === 1 && typeof color[0] === 'string') {
|
||
|
return color[0];
|
||
|
}
|
||
|
|
||
|
if (color.length === 1 && typeof color[0] === 'number') {
|
||
|
color.push(color[0]);
|
||
|
}
|
||
|
|
||
|
if (color.length === 2 && typeof color[0] === 'number') {
|
||
|
color.push(color[0]);
|
||
|
}
|
||
|
|
||
|
if (color.length === 3) {
|
||
|
return `${colorMode}(${color[0]}, ${color[1]}, ${color[2]})`;
|
||
|
}
|
||
|
|
||
|
return '#000000';
|
||
|
}
|
||
|
|
||
|
function drawArc(ctx, args, fill, stroke) {
|
||
|
const {
|
||
|
x,
|
||
|
y,
|
||
|
radius,
|
||
|
startAngle,
|
||
|
endAngle,
|
||
|
close,
|
||
|
} = args;
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(x, y, radius, startAngle, endAngle);
|
||
|
|
||
|
if (close) {
|
||
|
ctx.lineTo(x, y);
|
||
|
ctx.closePath();
|
||
|
}
|
||
|
|
||
|
if (fill) {
|
||
|
ctx.fill();
|
||
|
}
|
||
|
if (stroke) {
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function drawCircle(ctx, args, fill, stroke) {
|
||
|
const {
|
||
|
x,
|
||
|
y,
|
||
|
radius
|
||
|
} = args;
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
||
|
|
||
|
if (fill) {
|
||
|
ctx.fill();
|
||
|
}
|
||
|
if (stroke) {
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function drawShape(ctx, close, vertices, fill, stroke) {
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(...vertices[0]);
|
||
|
|
||
|
for (let i = 1; i < vertices.length; i++) {
|
||
|
ctx.lineTo(...vertices[i]);
|
||
|
}
|
||
|
|
||
|
if (close) {
|
||
|
ctx.closePath();
|
||
|
}
|
||
|
|
||
|
if (fill) {
|
||
|
ctx.fill();
|
||
|
}
|
||
|
if (stroke) {
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function drawRoundedRect(ctx, args, fill, stroke) {
|
||
|
const {
|
||
|
x,
|
||
|
y,
|
||
|
width,
|
||
|
height,
|
||
|
roundness
|
||
|
} = args;
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(x + roundness, y);
|
||
|
ctx.arc(x + width - roundness, y + roundness, roundness, -0.5 * Math.PI, 0);
|
||
|
ctx.arc(x + width - roundness, y + height - roundness, roundness, 0, 0.5 * Math.PI);
|
||
|
ctx.arc(x + roundness, y + height - roundness, roundness, 0.5 * Math.PI, Math.PI);
|
||
|
ctx.arc(x + roundness, y + roundness, roundness, Math.PI, 1.5 * Math.PI);
|
||
|
|
||
|
if (fill) {
|
||
|
ctx.fill();
|
||
|
}
|
||
|
if (stroke) {
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function drawText(ctx, args, fill, stroke, options) {
|
||
|
const {
|
||
|
x,
|
||
|
y,
|
||
|
string
|
||
|
} = args;
|
||
|
ctx.font = options.fontSize + 'px ' + options.font;
|
||
|
ctx.textAlign = options.alignment;
|
||
|
ctx.textBaseline = options.baseline;
|
||
|
|
||
|
if (fill) {
|
||
|
ctx.fillText(string, x, y);
|
||
|
}
|
||
|
if (stroke) {
|
||
|
ctx.strokeText(string, x, y);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function drawTriangle(ctx, args, fill, stroke) {
|
||
|
const {
|
||
|
x1,
|
||
|
y1,
|
||
|
x2,
|
||
|
y2,
|
||
|
x3,
|
||
|
y3
|
||
|
} = args;
|
||
|
drawShape(ctx, true, [
|
||
|
[x1, y1],
|
||
|
[x2, y2],
|
||
|
[x3, y3]
|
||
|
], fill, stroke)
|
||
|
}
|