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
JavaScript
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