Some configs
This commit is contained in:
parent
666b11a477
commit
f205d23179
|
|
@ -94,13 +94,20 @@ export class Camera extends GameObject {
|
||||||
|
|
||||||
lockToObj(object: GameObject) {
|
lockToObj(object: GameObject) {
|
||||||
this.lockedObject = object;
|
this.lockedObject = object;
|
||||||
console.log("Watching", object);
|
console.info("Watching", object);
|
||||||
}
|
}
|
||||||
|
|
||||||
unlock() {
|
unlock() {
|
||||||
this.lockedObject = null;
|
this.lockedObject = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.pos = new Vec(0, 0);
|
||||||
|
this.zoom = 1;
|
||||||
|
this.rotateAngle = 0;
|
||||||
|
this.applyTransformation();
|
||||||
|
}
|
||||||
|
|
||||||
screenToScene(pos: Vec) {
|
screenToScene(pos: Vec) {
|
||||||
return pos
|
return pos
|
||||||
.scale(1 / this.zoom)
|
.scale(1 / this.zoom)
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,11 @@
|
||||||
import { Canvas } from "@/canvas";
|
import { Canvas } from "@/canvas";
|
||||||
import { clamp } from "@/common/functions";
|
import { GameObject } from "@/common/gameobject";
|
||||||
import { Vec } from "@/common/vec";
|
import { Vec } from "@/common/vec";
|
||||||
import { desiredDensity, particleSize } from "@/game/fluids/fluids";
|
import { particleSize } from "@/game/fluids/fluids";
|
||||||
import { Swarm } from "@/game/swarm/swarm";
|
import { Swarm } from "@/game/swarm/swarm";
|
||||||
import { fillCircle, strokeLine } from "@/graphics";
|
import { fillCircle, strokeCircle, strokeLine } from "@/graphics";
|
||||||
|
|
||||||
|
|
||||||
const padding = 50;
|
|
||||||
const allowedSteer = 2 / 180;
|
|
||||||
const idlingUrgency = 0.001;
|
|
||||||
const idlingDistance = 20;
|
|
||||||
const desiredDistanceToEveryone = 30;
|
|
||||||
const flockPerceptionDistance = 150;
|
|
||||||
const behaveLikeFlockDoesUrgency = 0.8;
|
|
||||||
const epsilon = 1e-5;
|
|
||||||
export type NavigationType = 'stayInBounds' | 'doNotBumpIntoFlock' | 'keepFlockClose' | 'behaveLikeFlockDoes' | 'idling';
|
export type NavigationType = 'stayInBounds' | 'doNotBumpIntoFlock' | 'keepFlockClose' | 'behaveLikeFlockDoes' | 'idling';
|
||||||
export type NavigatingDesire = {
|
export type NavigatingDesire = {
|
||||||
pos: Vec;
|
pos: Vec;
|
||||||
|
|
@ -26,13 +19,14 @@ const navigationColorMap = {
|
||||||
behaveLikeFlockDoes: 'yellow',
|
behaveLikeFlockDoes: 'yellow',
|
||||||
idling: 'purple'
|
idling: 'purple'
|
||||||
};
|
};
|
||||||
export class Agent {
|
export class Agent extends GameObject {
|
||||||
dir: Vec;
|
dir: Vec;
|
||||||
pos: Vec;
|
|
||||||
swarm: Swarm;
|
swarm: Swarm;
|
||||||
idlingGoal: Vec|null = null;
|
idlingGoal: Vec|null = null;
|
||||||
lastNavigation: NavigatingDesire;
|
lastNavigation: NavigatingDesire;
|
||||||
|
lastFlockCenter: Vec = new Vec;
|
||||||
constructor(pos: Vec, swarm: Swarm) {
|
constructor(pos: Vec, swarm: Swarm) {
|
||||||
|
super();
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
this.swarm = swarm;
|
this.swarm = swarm;
|
||||||
this.lastNavigation = {
|
this.lastNavigation = {
|
||||||
|
|
@ -43,28 +37,61 @@ export class Agent {
|
||||||
this.dir = Vec.randomUnit();
|
this.dir = Vec.randomUnit();
|
||||||
}
|
}
|
||||||
|
|
||||||
update(canvas: Canvas, delta: DOMHighResTimeStamp) {
|
update({input}: Canvas) {
|
||||||
const nextPos = this.pos.add(this.dir);
|
const nextPos = this.pos.add(this.dir);
|
||||||
const navigations = [];
|
if (input.mouseDown) {
|
||||||
navigations.push(this.stayInBounds(nextPos));
|
this.steerTowards(input.mouseWorldPos);
|
||||||
navigations.push(this.doNotBumpIntoFlock(nextPos));
|
} else {
|
||||||
navigations.push(this.keepFlockClose(nextPos));
|
const navigations = [];
|
||||||
navigations.push(this.behaveLikeFlockDoes(nextPos));
|
navigations.push(this.stayInBounds(nextPos));
|
||||||
navigations.push(this.idling(nextPos));
|
navigations.push(this.doNotBumpIntoFlock(nextPos));
|
||||||
const navigation = this.mostUrgentNavigation(navigations);
|
navigations.push(this.keepFlockClose(nextPos));
|
||||||
this.steerTowards(navigation.pos);
|
navigations.push(this.behaveLikeFlockDoes(nextPos));
|
||||||
this.lastNavigation = navigation;
|
navigations.push(this.idling(nextPos));
|
||||||
|
const navigation = this.mostUrgentNavigation(navigations);
|
||||||
|
//const navigation = this.combinedNavigation(navigations, nextPos);
|
||||||
|
this.steerTowards(navigation.pos);
|
||||||
|
|
||||||
|
this.lastNavigation = navigation;
|
||||||
|
}
|
||||||
this.pos = nextPos;
|
this.pos = nextPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
steerTowards(target: Vec) {
|
steerTowards(target: Vec) {
|
||||||
const targetDir = target.sub(this.pos);
|
const targetDir = target.sub(this.pos);
|
||||||
// no division by zero!
|
// no division by zero!
|
||||||
if (targetDir.length() < epsilon) {
|
if (targetDir.length() < this.swarm.config.epsilon) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const alpha = this.dir.clockwiseAngleBetween(targetDir.normalize());
|
const alpha = this.dir.clockwiseAngleBetween(targetDir.normalize());
|
||||||
this.dir = this.dir.rotate(Math.min(allowedSteer, Math.abs(alpha)) * Math.sign(alpha)).normalize();
|
this.dir = this.dir.rotate(Math.min(this.swarm.config.allowedSteer, Math.abs(alpha)) * Math.sign(alpha)).normalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
combinedNavigation(navigations: NavigatingDesire[], nextPos: Vec) {
|
||||||
|
const mostUrgent = this.mostUrgentNavigation(navigations);
|
||||||
|
if (mostUrgent.urgency === 1) {
|
||||||
|
return mostUrgent;
|
||||||
|
}
|
||||||
|
const weightedDir = navigations
|
||||||
|
.reduce((sum, n) => {
|
||||||
|
const diff = n.pos.sub(this.pos);
|
||||||
|
if (diff.length() < this.swarm.config.epsilon) {
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
const dir = diff.normalize();
|
||||||
|
return sum.add(dir.scale(n.urgency));
|
||||||
|
}, new Vec);
|
||||||
|
let pos = this.pos.add(weightedDir.normalize());
|
||||||
|
if (pos.sub(this.pos).length() < this.swarm.config.epsilon) {
|
||||||
|
pos = nextPos;
|
||||||
|
}
|
||||||
|
const urgency = navigations
|
||||||
|
.reduce((sum, n) => sum + n.urgency, 0) / navigations.length;
|
||||||
|
return {
|
||||||
|
pos,
|
||||||
|
urgency,
|
||||||
|
type: mostUrgent.type,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
mostUrgentNavigation(navigations: NavigatingDesire[]) {
|
mostUrgentNavigation(navigations: NavigatingDesire[]) {
|
||||||
|
|
@ -74,7 +101,7 @@ export class Agent {
|
||||||
|
|
||||||
stayInBounds(nextPos: Vec): NavigatingDesire {
|
stayInBounds(nextPos: Vec): NavigatingDesire {
|
||||||
const {rect} = this.swarm;
|
const {rect} = this.swarm;
|
||||||
if (rect.contains(this.pos.add(this.dir.scale(padding)))) {
|
if (rect.contains(this.pos.add(this.dir.scale(this.swarm.config.padding)))) {
|
||||||
return {
|
return {
|
||||||
pos: nextPos,
|
pos: nextPos,
|
||||||
urgency: 0,
|
urgency: 0,
|
||||||
|
|
@ -91,7 +118,7 @@ export class Agent {
|
||||||
doNotBumpIntoFlock(nextPos: Vec): NavigatingDesire {
|
doNotBumpIntoFlock(nextPos: Vec): NavigatingDesire {
|
||||||
const {agents} = this.swarm;
|
const {agents} = this.swarm;
|
||||||
const visibleAgentsNotMe = agents
|
const visibleAgentsNotMe = agents
|
||||||
.filter(a => a.pos.distance(this.pos) < flockPerceptionDistance && a !== this);
|
.filter(a => a.pos.distance(this.pos) < this.swarm.config.flockPerceptionDistance && a !== this);
|
||||||
const visibleAgentsInBounds = visibleAgentsNotMe
|
const visibleAgentsInBounds = visibleAgentsNotMe
|
||||||
.filter(a => this.swarm.rect.contains(a.pos));
|
.filter(a => this.swarm.rect.contains(a.pos));
|
||||||
let closestAgent: Agent|null = null;
|
let closestAgent: Agent|null = null;
|
||||||
|
|
@ -108,9 +135,10 @@ export class Agent {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const awayFromClosestAgentDir = this.pos.sub(closestAgent.pos).normalize();
|
const awayFromClosestAgentDir = this.pos.sub(closestAgent.pos).normalize();
|
||||||
|
const urgency = Math.max(0, (this.swarm.config.desiredDistanceToEveryone - closestAgent.pos.distance(this.pos)) / this.swarm.config.desiredDistanceToEveryone);
|
||||||
return {
|
return {
|
||||||
pos: this.pos.add(awayFromClosestAgentDir),
|
pos: this.pos.add(awayFromClosestAgentDir),
|
||||||
urgency: (desiredDistanceToEveryone - closestAgent.pos.distance(this.pos)) / desiredDistanceToEveryone,
|
urgency: urgency * urgency,
|
||||||
type: 'doNotBumpIntoFlock'
|
type: 'doNotBumpIntoFlock'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -118,16 +146,18 @@ export class Agent {
|
||||||
keepFlockClose(nextPos: Vec): NavigatingDesire {
|
keepFlockClose(nextPos: Vec): NavigatingDesire {
|
||||||
const {agents} = this.swarm;
|
const {agents} = this.swarm;
|
||||||
const visibleAgents = agents
|
const visibleAgents = agents
|
||||||
.filter(a => a.pos.distance(this.pos) < flockPerceptionDistance);
|
.filter(a => a.pos.distance(this.pos) < this.swarm.config.flockPerceptionDistance);
|
||||||
const visibleAgentsInBounds = visibleAgents
|
const visibleAgentsInBounds = visibleAgents
|
||||||
.filter(a => this.swarm.rect.contains(a.pos));
|
.filter(a => this.swarm.rect.contains(a.pos));
|
||||||
const center = visibleAgentsInBounds
|
const center = visibleAgentsInBounds
|
||||||
.reduce((sum, a) => sum.add(a.pos), new Vec)
|
.reduce((sum, a) => sum.add(a.pos), new Vec)
|
||||||
.scale(1 / visibleAgentsInBounds.length);
|
.scale(1 / visibleAgentsInBounds.length);
|
||||||
const distanceToCenter = this.pos.distance(center);
|
const distanceToCenter = this.pos.distance(center);
|
||||||
|
this.lastFlockCenter = center;
|
||||||
|
let urgency = Math.max(0, (distanceToCenter - this.swarm.config.flockIsCloseEnoughDistance) / this.swarm.config.flockPerceptionDistance);
|
||||||
return {
|
return {
|
||||||
pos: center,
|
pos: center,
|
||||||
urgency: distanceToCenter / flockPerceptionDistance,
|
urgency: urgency * urgency,
|
||||||
type: 'keepFlockClose'
|
type: 'keepFlockClose'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +165,7 @@ export class Agent {
|
||||||
behaveLikeFlockDoes(nextPos: Vec): NavigatingDesire {
|
behaveLikeFlockDoes(nextPos: Vec): NavigatingDesire {
|
||||||
const {agents} = this.swarm;
|
const {agents} = this.swarm;
|
||||||
const visibleAgents = agents
|
const visibleAgents = agents
|
||||||
.filter(a => a.pos.distance(this.pos) < flockPerceptionDistance);
|
.filter(a => a.pos.distance(this.pos) < this.swarm.config.flockPerceptionDistance);
|
||||||
const visibleAgentsInBounds = visibleAgents
|
const visibleAgentsInBounds = visibleAgents
|
||||||
.filter(a => this.swarm.rect.contains(a.pos));
|
.filter(a => this.swarm.rect.contains(a.pos));
|
||||||
const averageDir = visibleAgentsInBounds
|
const averageDir = visibleAgentsInBounds
|
||||||
|
|
@ -144,23 +174,23 @@ export class Agent {
|
||||||
const angleDiff = this.dir.clockwiseAngleBetween(averageDir);
|
const angleDiff = this.dir.clockwiseAngleBetween(averageDir);
|
||||||
return {
|
return {
|
||||||
pos: nextPos.add(averageDir),
|
pos: nextPos.add(averageDir),
|
||||||
urgency: behaveLikeFlockDoesUrgency * Math.abs(angleDiff) / Math.PI,
|
urgency: this.swarm.config.behaveLikeFlockDoesUrgency * Math.abs(angleDiff) / Math.PI,
|
||||||
type: 'behaveLikeFlockDoes'
|
type: 'behaveLikeFlockDoes'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
idling(nextPos: Vec): NavigatingDesire {
|
idling(nextPos: Vec): NavigatingDesire {
|
||||||
if (this.idlingGoal && this.idlingGoal.distance(nextPos) > idlingDistance) {
|
if (this.idlingGoal && this.idlingGoal.distance(nextPos) > this.swarm.config.idlingDistance) {
|
||||||
return {
|
return {
|
||||||
pos: this.idlingGoal,
|
pos: this.idlingGoal,
|
||||||
urgency: idlingUrgency,
|
urgency: this.swarm.config.idlingUrgency,
|
||||||
type: 'idling'
|
type: 'idling'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
this.idlingGoal = Vec.random(this.swarm.rect);
|
this.idlingGoal = Vec.random(this.swarm.rect);
|
||||||
return {
|
return {
|
||||||
pos: this.idlingGoal,
|
pos: this.idlingGoal,
|
||||||
urgency: idlingUrgency,
|
urgency: this.swarm.config.idlingUrgency,
|
||||||
type: 'idling'
|
type: 'idling'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -172,21 +202,24 @@ export class Agent {
|
||||||
return 255 - this.lastNavigation.urgency * 255;
|
return 255 - this.lastNavigation.urgency * 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**render({ctx}: Canvas) {
|
render({ctx, input}: Canvas) {
|
||||||
|
if (input.mouseDown) {
|
||||||
}*/
|
fillCircle(ctx, this.pos, particleSize, 'gray');
|
||||||
|
|
||||||
render({ctx}: Canvas) {
|
|
||||||
if (this.swarm.renderStyle === 'satisfaction') {
|
|
||||||
const satisfaction = this.satisfaction();
|
|
||||||
const dissatisfaction = 255 - satisfaction;
|
|
||||||
fillCircle(ctx, this.pos, particleSize, `rgb(${dissatisfaction}, 0, ${satisfaction})`);
|
|
||||||
strokeLine(ctx, this.pos, this.pos.add(this.dir.scale(30)));
|
|
||||||
} else {
|
} else {
|
||||||
const color = navigationColorMap[this.lastNavigation.type];
|
if (this.swarm.config.renderStyle === 'satisfaction') {
|
||||||
fillCircle(ctx, this.pos, particleSize, color);
|
const satisfaction = this.satisfaction();
|
||||||
|
const dissatisfaction = 255 - satisfaction;
|
||||||
|
fillCircle(ctx, this.pos, particleSize, `rgb(${dissatisfaction}, 0, ${satisfaction})`);
|
||||||
|
} else {
|
||||||
|
const color = navigationColorMap[this.lastNavigation.type];
|
||||||
|
fillCircle(ctx, this.pos, particleSize, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.swarm.config.showDirections) {
|
||||||
strokeLine(ctx, this.pos, this.pos.add(this.dir.scale(30)));
|
strokeLine(ctx, this.pos, this.pos.add(this.dir.scale(30)));
|
||||||
}
|
}
|
||||||
|
if (this.swarm.config.showFlockCenter) {
|
||||||
|
strokeCircle(ctx, this.lastFlockCenter, particleSize, 'orange');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,34 +3,58 @@ import { GameObject } from "@/common/gameobject";
|
||||||
import { Rect } from "@/common/rect";
|
import { Rect } from "@/common/rect";
|
||||||
import { Vec } from "@/common/vec";
|
import { Vec } from "@/common/vec";
|
||||||
import { Agent } from "@/game/swarm/agent";
|
import { Agent } from "@/game/swarm/agent";
|
||||||
|
import { strokeCircle } from "@/graphics";
|
||||||
import Button from "@/ui/button";
|
import Button from "@/ui/button";
|
||||||
|
|
||||||
let numAgents = 20;
|
export type RenderStyle = 'satisfaction'|'navigationMode';
|
||||||
|
|
||||||
|
export class Config {
|
||||||
|
numAgents: number = 20;
|
||||||
|
padding: number = 50;
|
||||||
|
allowedSteer: number = 5 / 180;
|
||||||
|
idlingUrgency: number = 0.001;
|
||||||
|
idlingDistance: number = 20;
|
||||||
|
desiredDistanceToEveryone: number = 50;
|
||||||
|
renderStyle: RenderStyle = 'satisfaction';
|
||||||
|
showDirections: boolean = false;
|
||||||
|
showFlockCenter: boolean = false;
|
||||||
|
flockPerceptionDistance: number = 100;
|
||||||
|
flockIsCloseEnoughDistance: number = 20;
|
||||||
|
behaveLikeFlockDoesUrgency: number = 0.8;
|
||||||
|
epsilon: number = 1e-5;
|
||||||
|
}
|
||||||
|
|
||||||
export class Swarm extends GameObject {
|
export class Swarm extends GameObject {
|
||||||
rect: Rect;
|
rect: Rect;
|
||||||
agents: Agent[];
|
agents: Agent[];
|
||||||
renderStyle: 'satisfaction'|'navigationMode' = 'navigationMode';
|
selectedAgent: Agent | null = null;
|
||||||
|
config: Config;
|
||||||
constructor(canvas: Canvas) {
|
constructor(canvas: Canvas) {
|
||||||
super();
|
super();
|
||||||
this.rect = new Rect(0, 0, canvas.width, canvas.height);
|
this.rect = new Rect(0, 0, canvas.width, canvas.height);
|
||||||
this.agents = [];
|
this.agents = [];
|
||||||
this.init();
|
this.config = new Config;
|
||||||
|
this.init(canvas);
|
||||||
this.initUI(canvas);
|
this.initUI(canvas);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.swarm = this;
|
window.swarm = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init(canvas: Canvas) {
|
||||||
|
canvas.camera.reset();
|
||||||
|
this.agents.forEach(a => canvas.remove(a));
|
||||||
this.agents = [];
|
this.agents = [];
|
||||||
|
this.selectedAgent = null;
|
||||||
const rectInTheMiddle = this.rect.translate(this.rect.tl.scale(0.25)).scale(0.5);
|
const rectInTheMiddle = this.rect.translate(this.rect.tl.scale(0.25)).scale(0.5);
|
||||||
const cols = Math.floor(Math.sqrt(numAgents));
|
const cols = Math.floor(Math.sqrt(this.config.numAgents));
|
||||||
const rows = Math.floor(numAgents / cols);
|
const rows = Math.floor(this.config.numAgents / cols);
|
||||||
const spacing = rectInTheMiddle.width / cols;
|
const spacing = rectInTheMiddle.width / cols;
|
||||||
for (let x = 0; x < cols; x++) {
|
for (let x = 0; x < cols; x++) {
|
||||||
for (let y = 0; y < rows; y++) {
|
for (let y = 0; y < rows; y++) {
|
||||||
const pos = rectInTheMiddle.tl.add(x * spacing, y * spacing);
|
const pos = rectInTheMiddle.tl.add(x * spacing, y * spacing);
|
||||||
this.agents.push(new Agent(pos, this));
|
const agent = new Agent(pos, this);
|
||||||
|
this.agents.push(agent);
|
||||||
|
canvas.add(agent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -38,29 +62,56 @@ export class Swarm extends GameObject {
|
||||||
initUI(canvas: Canvas) {
|
initUI(canvas: Canvas) {
|
||||||
const buttonSize = new Vec(100, 40);
|
const buttonSize = new Vec(100, 40);
|
||||||
const buttons = [
|
const buttons = [
|
||||||
new Button("Reset", Rect.bySize(new Vec(10, 10), buttonSize), () => this.init()),
|
new Button("Reset", Rect.bySize(new Vec(10, 10), buttonSize), () => this.init(canvas)),
|
||||||
new Button("Toggle Colors", Rect.bySize(new Vec(110, 10), buttonSize), () => {
|
new Button("Toggle Colors", Rect.bySize(new Vec(110, 10), buttonSize), () => {
|
||||||
this.renderStyle = this.renderStyle === 'satisfaction' ? 'navigationMode' : 'satisfaction';
|
this.config.renderStyle = this.config.renderStyle === 'satisfaction' ? 'navigationMode' : 'satisfaction';
|
||||||
}),
|
}),
|
||||||
new Button("Less Agents", Rect.bySize(new Vec(10, 60), buttonSize), () => {
|
new Button("Less Agents", Rect.bySize(new Vec(10, 60), buttonSize), () => {
|
||||||
numAgents -= Math.max(1, numAgents / 10);
|
this.config.numAgents -= Math.max(1, this.config.numAgents / 10);
|
||||||
this.init();
|
this.init(canvas);
|
||||||
}),
|
}),
|
||||||
new Button("More Agents", Rect.bySize(new Vec(110, 60), buttonSize), () => {
|
new Button("More Agents", Rect.bySize(new Vec(110, 60), buttonSize), () => {
|
||||||
numAgents += Math.max(1, numAgents / 10);
|
this.config.numAgents += Math.max(1, this.config.numAgents / 10);
|
||||||
this.init();
|
this.init(canvas);
|
||||||
|
}),
|
||||||
|
new Button("toggle Directions", Rect.bySize(new Vec(10, 110), buttonSize), () => {
|
||||||
|
this.config.showDirections = !this.config.showDirections;
|
||||||
|
}),
|
||||||
|
new Button("toggle Flock Center", Rect.bySize(new Vec(110, 110), buttonSize), () => {
|
||||||
|
this.config.showFlockCenter = !this.config.showFlockCenter;
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
buttons.forEach(b => canvas.add(b, Canvas.LAYER_UI));
|
buttons.forEach(b => canvas.add(b, Canvas.LAYER_UI));
|
||||||
|
canvas.input.onRightClick((event ) => {
|
||||||
|
const mousePos = canvas.input.mouseWorldPos;
|
||||||
|
this.selectedAgent = this.agents.reduce((closest: Agent|null, a) => {
|
||||||
|
if (!closest) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
const dist = a.pos.distance(mousePos);
|
||||||
|
if (dist < closest.pos.distance(mousePos)) {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
return closest;
|
||||||
|
}, null);
|
||||||
|
if (this.selectedAgent) {
|
||||||
|
canvas.camera.lockToObj(this.selectedAgent);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
update(canvas: Canvas, delta: DOMHighResTimeStamp) {
|
update(canvas: Canvas, delta: DOMHighResTimeStamp) {
|
||||||
// simulation seems to be breaking when browser goes to sleep
|
|
||||||
delta = Math.min(delta, 10);
|
|
||||||
this.agents.forEach(p => p.update(canvas, delta));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(canvas: Canvas) {
|
render(canvas: Canvas) {
|
||||||
this.agents.forEach(p => p.render(canvas));
|
const {input, ctx} = canvas;
|
||||||
|
if (input.mouseDown) {
|
||||||
|
strokeCircle(ctx, input.mouseWorldPos, 40, 'white');
|
||||||
|
}
|
||||||
|
if (this.selectedAgent) {
|
||||||
|
canvas.ctx.strokeStyle = "darkgrey"
|
||||||
|
canvas.ctx.strokeRect(this.rect.x, this.rect.y, this.rect.width, this.rect.height);
|
||||||
|
strokeCircle(ctx, this.selectedAgent.pos, 20, 'purple');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
47
src/input.ts
47
src/input.ts
|
|
@ -34,33 +34,48 @@ export class Input {
|
||||||
document.addEventListener('keyup', event => {
|
document.addEventListener('keyup', event => {
|
||||||
this.keydowns.delete(event.key);
|
this.keydowns.delete(event.key);
|
||||||
});
|
});
|
||||||
document.addEventListener('mousemove', event => {
|
|
||||||
this._mousePos = new Vec(event.clientX, event.clientY);
|
const mouseUp = (event: MouseEvent|TouchEvent) => {
|
||||||
});
|
const button = event instanceof MouseEvent ? event.button : 0;
|
||||||
document.addEventListener('mouseup', event => {
|
if (button === 0) {
|
||||||
if (event.button === 0) {
|
|
||||||
this._mouseDown = false;
|
this._mouseDown = false;
|
||||||
} else if (event.button === 1) {
|
} else if (button === 1) {
|
||||||
this._middleMouseDown = false;
|
this._middleMouseDown = false;
|
||||||
this._middleMousePressed = false;
|
this._middleMousePressed = false;
|
||||||
} else if (event.button === 2) {
|
} else if (button === 2) {
|
||||||
this._rightMouseDown = false;
|
this._rightMouseDown = false;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
document.addEventListener('mousedown', event => {
|
const mouseMove = (event: MouseEvent|TouchEvent) => {
|
||||||
if (event.button === 0) {
|
if (event instanceof MouseEvent) {
|
||||||
|
this._mousePos = new Vec(event.clientX, event.clientY);
|
||||||
|
} else {
|
||||||
|
this._mousePos = new Vec(event.touches[0].clientX, event.touches[0].clientY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mouseDown = (event: MouseEvent|TouchEvent) => {
|
||||||
|
mouseMove(event);
|
||||||
|
const button = event instanceof MouseEvent ? event.button : 0;
|
||||||
|
if (button === 0) {
|
||||||
this._mouseDown = true;
|
this._mouseDown = true;
|
||||||
this._mousePressed = true;
|
this._mousePressed = true;
|
||||||
} else if (event.button === 1) {
|
} else if (button === 1) {
|
||||||
this._middleMouseDown = true;
|
this._middleMouseDown = true;
|
||||||
this._middleMousePressed = true;
|
this._middleMousePressed = true;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
} else if (event.button === 2) {
|
} else if (button === 2) {
|
||||||
this._rightMouseDown = true;
|
this._rightMouseDown = true;
|
||||||
this._rightMousePressed = true;
|
this._rightMousePressed = true;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
document.addEventListener('mousedown', mouseDown);
|
||||||
|
document.addEventListener('mousemove', mouseMove);
|
||||||
|
document.addEventListener('mouseup', mouseUp);
|
||||||
|
document.addEventListener('touchstart', mouseDown);
|
||||||
|
document.addEventListener('touchmove', mouseMove);
|
||||||
|
document.addEventListener('touchend', mouseUp);
|
||||||
|
document.addEventListener('touchcancel', mouseUp);
|
||||||
document.addEventListener('contextmenu', event => {
|
document.addEventListener('contextmenu', event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
@ -117,7 +132,11 @@ export class Input {
|
||||||
document.addEventListener('click', callback);
|
document.addEventListener('click', callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRightClick(callback: (event: MouseEvent) => void) {
|
||||||
|
document.addEventListener('contextmenu', callback);
|
||||||
|
}
|
||||||
|
|
||||||
onWheel(callback: (event: WheelEvent) => void) {
|
onWheel(callback: (event: WheelEvent) => void) {
|
||||||
document.addEventListener('wheel', callback);
|
document.addEventListener('wheel', callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user