Skip to content

Commit

Permalink
Base Strategy Pattern implementation for WebGPU/Canvas rendering & sh…
Browse files Browse the repository at this point in the history
…ape draws.
  • Loading branch information
ZainGS committed Aug 12, 2024
1 parent 43a6679 commit 92a0c5a
Show file tree
Hide file tree
Showing 21 changed files with 2,008 additions and 415 deletions.
1,360 changes: 1,260 additions & 100 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
"test": "vitest"
},
"devDependencies": {
"typescript": "^5.5.3",
"vite": "^5.4.0",
"@webgpu/types": "^0.1.44",
"eslint": "^8.0.0",
"prettier": "^2.0.0"
"prettier": "^2.0.0",
"typescript": "^5.5.3",
"vite": "^5.4.0"
},
"dependencies": {
"@wasm-package": "file:wasm/pkg"
"wasm-package": "file:wasm/pkg"
},
"files": [
"dist",
Expand Down
104 changes: 100 additions & 4 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,55 @@
// src/main.ts
import { Renderer } from './renderer/renderer';

// src/main.ts
import { SceneGraph } from './scene-graph/scene-graph';
import { Rectangle } from './scene-graph/rectangle';
import { WebGPURenderer } from './renderer/webgpu-renderer';
import { WebGPURenderStrategy } from './renderer/webgpu-render-strategy';

async function main() {
// Set up the canvas and WebGPURenderer
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
const webgpuRenderer = new WebGPURenderer(canvas);
await webgpuRenderer.initialize();

const device = webgpuRenderer.getDevice();
const pipeline = webgpuRenderer.getPipeline();

// Create the WebGPU render strategy
const webGPURenderStrategy = new WebGPURenderStrategy(device, pipeline);

// Create the scene graph
const sceneGraph = new SceneGraph(webGPURenderStrategy);

// Create a rectangle with the WebGPU render strategy

const rect = new Rectangle(webGPURenderStrategy, 100, 50, 'red', 'black', 2);
rect.x = 150;
rect.y = 100;

// Add the rectangle to the scene graph
sceneGraph.root.addChild(rect);

// Start the rendering loop
function renderLoop() {
webgpuRenderer.render(sceneGraph);
requestAnimationFrame(renderLoop);
}
renderLoop();
}

main();

/*
///////////////////////////////////////////////////////////////////////////
CANVAS
///////////////////////////////////////////////////////////////////////////
import { SceneGraph } from './scene-graph/scene-graph';
import { Rectangle } from './scene-graph/rectangle';
import { CanvasRenderer } from './renderer/canvas-renderer';
import { CanvasRenderStrategy } from './renderer/canvas-render-strategy';
// Set up the canvas
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
Expand All @@ -11,8 +59,11 @@ canvas.height = 600;
// Create the scene graph
const sceneGraph = new SceneGraph();
// Create the canvas render strategy
const canvasRenderStrategy = new CanvasRenderStrategy();
// Create a rectangle
const rect = new Rectangle(100, 50, 'red', 'black', 2);
const rect = new Rectangle(canvasRenderStrategy, 100, 50, 'red', 'black', 2);
rect.x = 150;
rect.y = 100;
Expand All @@ -26,7 +77,52 @@ rect.onClick = () => {
sceneGraph.root.addChild(rect);
// Create the renderer
const renderer = new Renderer(canvas, sceneGraph);
const renderer = new CanvasRenderer(canvas, sceneGraph);
// Start the rendering loop manually
renderer.start();
renderer.start();
/////////////////////////////////////////////////////////////////////////////
WEBGPU
/////////////////////////////////////////////////////////////////////////////
// src/main.ts
import { SceneGraph } from './scene-graph/scene-graph';
import { Rectangle } from './scene-graph/rectangle';
import { WebGPURenderer } from './renderer/webgpu-renderer';
import { WebGPURenderStrategy } from './rendering/webgpu-render-strategy';
async function main() {
// Set up the canvas and WebGPURenderer
const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
const webgpuRenderer = new WebGPURenderer(canvas);
await webgpuRenderer.initialize();
const device = webgpuRenderer.getDevice();
const pipeline = webgpuRenderer.getPipeline();
// Create the scene graph
const sceneGraph = new SceneGraph();
// Create the WebGPU render strategy
const webGPURenderStrategy = new WebGPURenderStrategy(device, pipeline);
// Create a rectangle with the WebGPU render strategy
const rect = new Rectangle(webGPURenderStrategy, 100, 50, 'red', 'black', 2);
rect.x = 150;
rect.y = 100;
// Add the rectangle to the scene graph
sceneGraph.root.addChild(rect);
// Start the rendering loop
function renderLoop() {
webgpuRenderer.render(sceneGraph);
requestAnimationFrame(renderLoop);
}
renderLoop();
}
main();
*/
103 changes: 103 additions & 0 deletions src/renderer/canvas-render-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// src/rendering/canvas-render-strategy.ts
// Handles rendering of shapes for the Canvas Render Strategy.

import { RenderStrategy } from './render-strategy';
import { Node } from '../scene-graph/node';
import { Rectangle } from '../scene-graph/rectangle';
import { Circle } from '../scene-graph/circle';
import { Line } from '../scene-graph/line';
import { Polygon } from '../scene-graph/polygon';

export class CanvasRenderStrategy implements RenderStrategy {
render(node: Node, ctxOrEncoder: CanvasRenderingContext2D | GPURenderPassEncoder, pipeline?: GPURenderPipeline): void {
if (ctxOrEncoder instanceof CanvasRenderingContext2D) {
if (!node.visible) return;

const ctx = ctxOrEncoder; // Type is now CanvasRenderingContext2D

ctx.save();

// Apply transformations
ctx.translate(node.x, node.y);
ctx.rotate(node.rotation);
ctx.scale(node.scaleX, node.scaleY);

// Check for specific node types and call the appropriate draw logic
if (node instanceof Rectangle) {
this.drawRectangle(node, ctx);
} else if (node instanceof Circle) {
this.drawCircle(node, ctx);
} else if (node instanceof Line) {
this.drawLine(node, ctx);
} else if (node instanceof Polygon) {
this.drawPolygon(node, ctx);
}

// Render children
node.children.forEach(child => this.render(child, ctx));

ctx.restore();
}
}

private drawRectangle(rect: Rectangle, ctx: CanvasRenderingContext2D) {
if (rect.fillColor !== 'transparent') {
ctx.fillStyle = rect.fillColor;
ctx.fillRect(0, 0, rect.boundingBox!.width, rect.boundingBox!.height);
}

if (rect.strokeWidth > 0) {
ctx.strokeStyle = rect.strokeColor;
ctx.lineWidth = rect.strokeWidth;
ctx.strokeRect(0, 0, rect.boundingBox!.width, rect.boundingBox!.height);
}
}

private drawCircle(circle: Circle, ctx: CanvasRenderingContext2D) {
if (circle.fillColor !== 'transparent') {
ctx.fillStyle = circle.fillColor;
ctx.beginPath();
ctx.arc(circle.boundingBox!.width / 2, circle.boundingBox!.height / 2, circle.boundingBox!.width / 2, 0, Math.PI * 2);
ctx.fill();
}

if (circle.strokeWidth > 0) {
ctx.strokeStyle = circle.strokeColor;
ctx.lineWidth = circle.strokeWidth;
ctx.stroke();
}
}

private drawLine(line: Line, ctx: CanvasRenderingContext2D) {
ctx.beginPath();
ctx.moveTo(line.startX, line.startY);
ctx.lineTo(line.endX, line.endY);
ctx.strokeStyle = line.strokeColor;
ctx.lineWidth = line.strokeWidth;
ctx.stroke();
}

private drawPolygon(polygon: Polygon, ctx: CanvasRenderingContext2D) {
if (polygon.points.length < 2) return;

ctx.beginPath();
ctx.moveTo(polygon.points[0].x, polygon.points[0].y);

for (let i = 1; i < polygon.points.length; i++) {
ctx.lineTo(polygon.points[i].x, polygon.points[i].y);
}

ctx.closePath();

if (polygon.fillColor !== 'transparent') {
ctx.fillStyle = polygon.fillColor;
ctx.fill();
}

if (polygon.strokeWidth > 0) {
ctx.strokeStyle = polygon.strokeColor;
ctx.lineWidth = polygon.strokeWidth;
ctx.stroke();
}
}
}
Loading

0 comments on commit 92a0c5a

Please sign in to comment.