UNPKG

agentscape

Version:

Agentscape is a library for creating agent-based simulations. It provides a simple API for defining agents and their behavior, and for defining the environment in which the agents interact. Agentscape is designed to be flexible and extensible, allowing

265 lines 10.9 kB
import { Angle, Color } from '../../numbers'; import { AgentStyle } from '../../entities/Agent'; export class Render2D { constructor(opts) { const { root, worldWidth, renderHeight, renderWidth, title, id, autoPlay, frameRate } = opts; // create a draggable canvas element const draggable = document.createElement('drag-pane'); draggable.style.zIndex = '1'; draggable.id = 'canvas_main'; draggable.setAttribute('heading', title); draggable.setAttribute('key', id); this.renderHeight = renderHeight; this.renderWidth = renderWidth; this.cellWidth = renderWidth / worldWidth; this.cellHeight = renderHeight / worldWidth; const canvas = document.createElement('canvas'); this.ctx = canvas.getContext('2d'); canvas.width = renderWidth; canvas.height = renderHeight; // create a click event handler for the canvas canvas.addEventListener('click', (e) => { const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; // dispatch a custom event with the clicked cell coordinates const event = new CustomEvent('inspectClick', { detail: [Math.floor(x / this.cellWidth), Math.floor(y / this.cellHeight)] }); if (opts.onCellClick) { opts.onCellClick([Math.floor(x / this.cellWidth), Math.floor(y / this.cellHeight)], this); } window.dispatchEvent(event); }); draggable.appendChild(canvas); const controlBar = document.createElement('animation-toolbar'); controlBar.setAttribute('autoPlay', autoPlay.toString()); controlBar.setAttribute('fps', frameRate.toString()); draggable.appendChild(controlBar); root.appendChild(draggable); } clear() { this.ctx.clearRect(0, 0, this.renderWidth, this.renderHeight); } drawCellGrid(world, opts = {}) { const { colorFunction } = opts; for (let x = 0; x < world.width; x++) { for (let y = 0; y < world.height; y++) { const cell = world.getCell([x, y]); const color = colorFunction ? colorFunction(cell) : { fill: cell.color, stroke: cell.strokeColor }; this.drawRectangle(x, y, { width: this.cellWidth, height: this.cellHeight, fill: color.fill, stroke: color.stroke }); } } } /** * Draws agents on the canvas * @param agents */ drawAgentSet(agents, opts = {}) { const { colorFunction, styleFunction } = opts; agents.forEach(agent => { this.drawAgent(agent, { colorFunction, styleFunction }); }); } drawAgent(agent, opts = {}) { const { colorFunction, styleFunction } = opts; const color = colorFunction ? colorFunction(agent) : { fill: agent.color, stroke: agent.strokeColor }; const style = styleFunction ? styleFunction(agent) : agent.style; const [x, y] = agent.position.components; const options = { width: agent.radius, height: agent.radius, radius: agent.radius, rotation: agent.rotation, fill: color.fill, stroke: color.stroke }; if (style === AgentStyle.CIRCLE) { this.drawCircle(x, y, options); } if (style === AgentStyle.TRIANGLE) { this.drawTriangle(x, y, options); } if (style === AgentStyle.SQUARE) { this.drawRectangle(x, y, options); } } drawCircle(x, y, options) { const { radius = 1, rotation = new Angle(0, 'rad'), fill = Color.fromName('blue'), stroke = undefined, showRotation = true, lineWidth = 1 } = options !== null && options !== void 0 ? options : {}; const _x = x * this.cellWidth; const _y = y * this.cellHeight; const _radius = radius * (this.cellWidth / 2); this.ctx.fillStyle = fill.toRGB(); this.ctx.beginPath(); this.ctx.arc(_x, _y, _radius, 0, 2 * Math.PI); this.ctx.fill(); if (stroke) { this.ctx.strokeStyle = stroke.toRGB(); this.ctx.stroke(); } // draw a line from the center of the circle to the edge to show rotation if (rotation && showRotation) { this.ctx.beginPath(); this.ctx.moveTo(_x, _y); this.ctx.lineTo(_x + _radius * Math.cos(rotation.asRadians()), _y + _radius * Math.sin(rotation.asRadians())); } if (stroke) { this.ctx.strokeStyle = stroke.toRGB(); this.ctx.lineWidth = lineWidth; this.ctx.stroke(); } } /** * Draws a unit rectangle centered at x,y */ drawRectangle(x, y, options) { const { width = 1, height = 1, rotation = new Angle(0, 'rad'), fill = Color.fromName('blue'), stroke = undefined, lineWidth = 1 } = options !== null && options !== void 0 ? options : {}; const _x = x * this.cellWidth; const _y = y * this.cellHeight; const _width = width * this.cellWidth; const _height = height * this.cellHeight; this.ctx.fillStyle = fill.toRGB(); this.ctx.fillRect(_x, _y, _width, _height); if (stroke) { this.ctx.strokeStyle = stroke.toRGB(); this.ctx.strokeRect(_x, _y, _width, _height); } // draw a line from the center of the rectangle to the edge to show rotation if (rotation) { this.ctx.beginPath(); this.ctx.moveTo(_x + _width / 2, _y + _height / 2); this.ctx.lineTo(_x + _width / 2 + _width / 2 * Math.cos(rotation.asRadians()), _y + _height / 2 + _height / 2 * Math.sin(rotation.asRadians())); if (stroke) { this.ctx.strokeStyle = stroke.toRGB(); this.ctx.lineWidth = lineWidth; this.ctx.stroke(); } } } drawLine(x1, y1, x2, y2, stroke, options) { var _a; this.ctx.beginPath(); this.ctx.moveTo(x1 * this.cellWidth, y1 * this.cellHeight); this.ctx.lineTo(x2 * this.cellWidth, y2 * this.cellHeight); this.ctx.strokeStyle = stroke.toRGB(); this.ctx.lineWidth = (_a = options === null || options === void 0 ? void 0 : options.lineWidth) !== null && _a !== void 0 ? _a : 1; this.ctx.stroke(); } drawTriangle(x, y, options) { var _a; const { width = 1, rotation = new Angle(0, 'rad'), fill = Color.fromName('blue'), stroke = undefined, } = options; const _width = width * this.cellWidth; const _x = x * this.cellWidth; const _y = y * this.cellHeight; // Define the triangle's vertices relative to the origin const halfWidth = _width / 2; const height = (Math.sqrt(3) / 2) * _width + _width / 2; // Height of an equilateral triangle const vertices = [ { x: 0, y: -height / 2 }, // Top vertex { x: -halfWidth, y: height / 2 }, // Bottom-left vertex { x: halfWidth, y: height / 2 } // Bottom-right vertex ]; // Save the current state of the canvas this.ctx.save(); // Translate to the origin this.ctx.translate(_x, _y); // Rotate by R radians this.ctx.rotate(rotation.asDegrees()); // Begin drawing the triangle this.ctx.beginPath(); this.ctx.moveTo(vertices[0].x, vertices[0].y); // Move to the first vertex for (let i = 1; i < vertices.length; i++) { this.ctx.lineTo(vertices[i].x, vertices[i].y); } this.ctx.closePath(); // Fill and stroke the triangle this.ctx.fillStyle = fill.toRGB(); this.ctx.fill(); this.ctx.strokeStyle = stroke.toRGB(); this.ctx.lineWidth = (_a = options === null || options === void 0 ? void 0 : options.lineWidth) !== null && _a !== void 0 ? _a : 1; this.ctx.stroke(); // Restore the canvas to its original state this.ctx.restore(); } drawPolygon(points, options) { var _a; const { fill = Color.fromName('blue'), stroke = undefined } = options !== null && options !== void 0 ? options : {}; if (points.length < 3) { throw new Error('A polygon must have at least 3 vertices'); } // Begin the path this.ctx.beginPath(); // Move to the first point const firstPoint = points[0]; const x0 = firstPoint[0] * this.cellWidth; const y0 = firstPoint[1] * this.cellHeight; this.ctx.moveTo(x0, y0); // Draw lines to the remaining points for (let i = 1; i < points.length; i++) { const point = points[i]; const x = point[0] * this.cellWidth; const y = point[1] * this.cellHeight; this.ctx.lineTo(x, y); } // Close the path this.ctx.closePath(); // Fill the polygon if (fill) { this.ctx.fillStyle = fill.toRGB(); this.ctx.fill(); } // Stroke the polygon if (stroke) { this.ctx.strokeStyle = stroke.toRGB(); this.ctx.lineWidth = (_a = options === null || options === void 0 ? void 0 : options.lineWidth) !== null && _a !== void 0 ? _a : 1; this.ctx.stroke(); } // Restore the context this.ctx.restore(); } drawPolyline(points, stroke, options) { var _a; if (points.length < 2) { throw new Error('A polyline must have at least 2 vertices'); } // Begin the path this.ctx.beginPath(); // Move to the first point const firstPoint = points[0]; const x0 = firstPoint[0] * this.cellWidth; const y0 = firstPoint[1] * this.cellHeight; this.ctx.moveTo(x0, y0); // Draw lines to the remaining points for (let i = 1; i < points.length; i++) { const point = points[i]; const x = point[0] * this.cellWidth; const y = point[1] * this.cellHeight; this.ctx.lineTo(x, y); } // Stroke the polyline this.ctx.lineWidth = (_a = options === null || options === void 0 ? void 0 : options.lineWidth) !== null && _a !== void 0 ? _a : 1; this.ctx.strokeStyle = stroke.toRGB(); this.ctx.stroke(); // Restore the context this.ctx.restore(); } } export default Render2D; //# sourceMappingURL=Render2D.js.map