This repository has been archived on 2021-10-15. You can view files and clone it, but cannot push or open issues or pull requests.
2020-coding-projects/uno/client/render.js
2020-05-05 22:35:48 +02:00

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)
}