ShatteredSword/src/Wolfie2D/Rendering/CanvasRenderer.ts
2021-03-18 17:28:05 -04:00

255 lines
9.3 KiB
TypeScript

import Map from "../DataTypes/Map";
import CanvasNode from "../Nodes/CanvasNode";
import Graphic from "../Nodes/Graphic";
import Point from "../Nodes/Graphics/Point";
import Rect from "../Nodes/Graphics/Rect";
import Sprite from "../Nodes/Sprites/Sprite";
import Tilemap from "../Nodes/Tilemap";
import OrthogonalTilemap from "../Nodes/Tilemaps/OrthogonalTilemap";
import UIElement from "../Nodes/UIElement";
import UILayer from "../Scene/Layers/UILayer";
import Scene from "../Scene/Scene";
import GraphicRenderer from "./CanvasRendering/GraphicRenderer";
import RenderingManager from "./RenderingManager"
import TilemapRenderer from "./CanvasRendering/TilemapRenderer";
import UIElementRenderer from "./CanvasRendering/UIElementRenderer";
import Label from "../Nodes/UIElements/Label";
import Button from "../Nodes/UIElements/Button";
import Slider from "../Nodes/UIElements/Slider";
import TextInput from "../Nodes/UIElements/TextInput";
import AnimatedSprite from "../Nodes/Sprites/AnimatedSprite";
import Vec2 from "../DataTypes/Vec2";
import Color from "../Utils/Color";
import Line from "../Nodes/Graphics/Line";
import Debug from "../Debug/Debug";
/**
* An implementation of the RenderingManager class using CanvasRenderingContext2D.
*/
export default class CanvasRenderer extends RenderingManager {
protected ctx: CanvasRenderingContext2D;
protected graphicRenderer: GraphicRenderer;
protected tilemapRenderer: TilemapRenderer;
protected uiElementRenderer: UIElementRenderer;
protected origin: Vec2;
protected zoom: number;
protected worldSize: Vec2;
constructor(){
super();
}
// @override
setScene(scene: Scene){
this.scene = scene;
this.graphicRenderer.setScene(scene);
this.tilemapRenderer.setScene(scene);
this.uiElementRenderer.setScene(scene);
}
// @override
initializeCanvas(canvas: HTMLCanvasElement, width: number, height: number): CanvasRenderingContext2D {
canvas.width = width;
canvas.height = height;
this.worldSize = new Vec2(width, height);
this.ctx = canvas.getContext("2d");
this.graphicRenderer = new GraphicRenderer(this.ctx);
this.tilemapRenderer = new TilemapRenderer(this.ctx);
this.uiElementRenderer = new UIElementRenderer(this.ctx)
// For crisp pixel art
this.ctx.imageSmoothingEnabled = false;
return this.ctx;
}
// @override
render(visibleSet: CanvasNode[], tilemaps: Tilemap[], uiLayers: Map<UILayer>): void {
// Sort by depth, then by visible set by y-value
visibleSet.sort((a, b) => {
if(a.getLayer().getDepth() === b.getLayer().getDepth()){
return (a.boundary.bottom) - (b.boundary.bottom);
} else {
return a.getLayer().getDepth() - b.getLayer().getDepth();
}
});
let tilemapIndex = 0;
let tilemapLength = tilemaps.length;
let visibleSetIndex = 0;
let visibleSetLength = visibleSet.length;
while(tilemapIndex < tilemapLength || visibleSetIndex < visibleSetLength){
// Check conditions where we've already reached the edge of one list
if(tilemapIndex >= tilemapLength){
// Only render the remaining visible set
let node = visibleSet[visibleSetIndex++];
if(node.visible){
this.renderNode(node);
}
continue;
}
if(visibleSetIndex >= visibleSetLength){
// Only render tilemaps
this.renderTilemap(tilemaps[tilemapIndex++]);
continue;
}
// Render whichever is further down
if(tilemaps[tilemapIndex].getLayer().getDepth() <= visibleSet[visibleSetIndex].getLayer().getDepth()){
this.renderTilemap(tilemaps[tilemapIndex++]);
} else {
let node = visibleSet[visibleSetIndex++];
if(node.visible){
this.renderNode(node);
}
}
}
// Render the uiLayers on top of everything else
let sortedUILayers = new Array<UILayer>();
uiLayers.forEach(key => sortedUILayers.push(uiLayers.get(key)));
sortedUILayers = sortedUILayers.sort((ui1, ui2) => ui1.getDepth() - ui2.getDepth());
sortedUILayers.forEach(uiLayer => {
if(!uiLayer.isHidden())
uiLayer.getItems().forEach(node => {
if((<CanvasNode>node).visible){
this.renderNode(<CanvasNode>node)
}
})
});
}
/**
* Renders a specified CanvasNode
* @param node The CanvasNode to render
*/
protected renderNode(node: CanvasNode): void {
// Calculate the origin of the viewport according to this sprite
this.origin = this.scene.getViewTranslation(node);
// Get the zoom level of the scene
this.zoom = this.scene.getViewScale();
// Move the canvas to the position of the node and rotate
let xScale = 1;
let yScale = 1;
if(node instanceof Sprite){
xScale = node.invertX ? -1 : 1;
yScale = node.invertY ? -1 : 1;
}
this.ctx.setTransform(xScale, 0, 0, yScale, (node.position.x - this.origin.x)*this.zoom, (node.position.y - this.origin.y)*this.zoom);
this.ctx.rotate(-node.rotation);
let globalAlpha = this.ctx.globalAlpha;
if(node instanceof Rect){
Debug.log("node" + node.id, "Node" + node.id + " Alpha: " + node.alpha);
}
this.ctx.globalAlpha = node.alpha;
if(node instanceof AnimatedSprite){
this.renderAnimatedSprite(<AnimatedSprite>node);
} else if(node instanceof Sprite){
this.renderSprite(<Sprite>node);
} else if(node instanceof Graphic){
this.renderGraphic(<Graphic>node);
} else if(node instanceof UIElement){
this.renderUIElement(<UIElement>node);
}
this.ctx.globalAlpha = globalAlpha;
this.ctx.setTransform(1, 0, 0, 1, 0, 0);
}
// @override
protected renderSprite(sprite: Sprite): void {
// Get the image from the resource manager
let image = this.resourceManager.getImage(sprite.imageId);
/*
Coordinates in the space of the image:
image crop start -> x, y
image crop size -> w, h
Coordinates in the space of the world
image draw start -> x, y
image draw size -> w, h
*/
this.ctx.drawImage(image,
sprite.imageOffset.x, sprite.imageOffset.y,
sprite.size.x, sprite.size.y,
(-sprite.size.x*sprite.scale.x/2)*this.zoom, (-sprite.size.y*sprite.scale.y/2)*this.zoom,
sprite.size.x * sprite.scale.x*this.zoom, sprite.size.y * sprite.scale.y*this.zoom);
}
// @override
protected renderAnimatedSprite(sprite: AnimatedSprite): void {
// Get the image from the resource manager
let image = this.resourceManager.getImage(sprite.imageId);
let animationIndex = sprite.animation.getIndexAndAdvanceAnimation();
let animationOffset = sprite.getAnimationOffset(animationIndex);
/*
Coordinates in the space of the image:
image crop start -> x, y
image crop size -> w, h
Coordinates in the space of the world (given we moved)
image draw start -> -w/2, -h/2
image draw size -> w, h
*/
this.ctx.drawImage(image,
sprite.imageOffset.x + animationOffset.x, sprite.imageOffset.y + animationOffset.y,
sprite.size.x, sprite.size.y,
(-sprite.size.x*sprite.scale.x/2)*this.zoom, (-sprite.size.y*sprite.scale.y/2)*this.zoom,
sprite.size.x * sprite.scale.x*this.zoom, sprite.size.y * sprite.scale.y*this.zoom);
}
// @override
protected renderGraphic(graphic: Graphic): void {
if(graphic instanceof Point){
this.graphicRenderer.renderPoint(<Point>graphic, this.zoom);
} else if(graphic instanceof Line){
this.graphicRenderer.renderLine(<Line>graphic, this.origin, this.zoom);
} else if(graphic instanceof Rect){
this.graphicRenderer.renderRect(<Rect>graphic, this.zoom);
}
}
// @override
protected renderTilemap(tilemap: Tilemap): void {
if(tilemap instanceof OrthogonalTilemap){
this.tilemapRenderer.renderOrthogonalTilemap(<OrthogonalTilemap>tilemap);
}
}
// @override
protected renderUIElement(uiElement: UIElement): void {
if(uiElement instanceof Label){
this.uiElementRenderer.renderLabel(uiElement);
} else if(uiElement instanceof Button){
this.uiElementRenderer.renderButton(uiElement);
} else if(uiElement instanceof Slider){
this.uiElementRenderer.renderSlider(uiElement);
} else if(uiElement instanceof TextInput){
this.uiElementRenderer.renderTextInput(uiElement);
}
}
clear(clearColor: Color): void {
this.ctx.clearRect(0, 0, this.worldSize.x, this.worldSize.y);
this.ctx.fillStyle = clearColor.toString();
this.ctx.fillRect(0, 0, this.worldSize.x, this.worldSize.y);
}
}