Make some schnecken
This commit is contained in:
parent
f205d23179
commit
0eed2881ad
BIN
src/assets/schnecke.png
Normal file
BIN
src/assets/schnecke.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
|
|
@ -164,13 +164,13 @@ export class Vec {
|
||||||
distance(x: number, y: number): number;
|
distance(x: number, y: number): number;
|
||||||
distance(x: number|Vec, y?: number): number {
|
distance(x: number|Vec, y?: number): number {
|
||||||
if (x instanceof Vec) {
|
if (x instanceof Vec) {
|
||||||
return this.sub(x).length();
|
return Math.sqrt((this.x - x.x) * (this.x - x.x) + (this.y - x.y) * (this.y - x.y));
|
||||||
}
|
}
|
||||||
if (undefined === y) {
|
if (undefined === y) {
|
||||||
throw new Error("y-coordinate undefined on vector operation");
|
throw new Error("y-coordinate undefined on vector operation");
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.sub(x, y).length();
|
return Math.sqrt((this.x - x) * (this.x - x) + (this.y - y) * (this.y - y));
|
||||||
}
|
}
|
||||||
|
|
||||||
rotate(angle: number) {
|
rotate(angle: number) {
|
||||||
|
|
|
||||||
1
src/env.d.ts
vendored
Normal file
1
src/env.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
declare module "*.png";
|
||||||
|
|
@ -9,6 +9,7 @@ const gravity = 0.008;
|
||||||
const numParticles = 200;
|
const numParticles = 200;
|
||||||
const pressureForce = 8000;
|
const pressureForce = 8000;
|
||||||
export const particleSize = 10;
|
export const particleSize = 10;
|
||||||
|
export const imageSize = 64;
|
||||||
export const desiredDensity = 6;
|
export const desiredDensity = 6;
|
||||||
const densityRadius = 40;
|
const densityRadius = 40;
|
||||||
const mass = 1;
|
const mass = 1;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import { Canvas } from "@/canvas";
|
import { Canvas } from "@/canvas";
|
||||||
import { GameObject } from "@/common/gameobject";
|
import { GameObject } from "@/common/gameobject";
|
||||||
import { Vec } from "@/common/vec";
|
import { Vec } from "@/common/vec";
|
||||||
import { particleSize } from "@/game/fluids/fluids";
|
import { imageSize, particleSize } from "@/game/fluids/fluids";
|
||||||
import { Swarm } from "@/game/swarm/swarm";
|
import { Swarm } from "@/game/swarm/swarm";
|
||||||
import { fillCircle, strokeCircle, strokeLine } from "@/graphics";
|
import { drawImageRotated, fillCircle, strokeCircle, strokeLine } from "@/graphics";
|
||||||
|
import Schnecke from "@/assets/schnecke.png";
|
||||||
|
|
||||||
|
const schneckenImage = new Image();
|
||||||
|
schneckenImage.src = Schnecke;
|
||||||
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;
|
||||||
|
|
@ -117,14 +119,20 @@ export class Agent extends GameObject {
|
||||||
|
|
||||||
doNotBumpIntoFlock(nextPos: Vec): NavigatingDesire {
|
doNotBumpIntoFlock(nextPos: Vec): NavigatingDesire {
|
||||||
const {agents} = this.swarm;
|
const {agents} = this.swarm;
|
||||||
const visibleAgentsNotMe = agents
|
const visibleAgentsInBounds = [];
|
||||||
.filter(a => a.pos.distance(this.pos) < this.swarm.config.flockPerceptionDistance && a !== this);
|
|
||||||
const visibleAgentsInBounds = visibleAgentsNotMe
|
|
||||||
.filter(a => this.swarm.rect.contains(a.pos));
|
|
||||||
let closestAgent: Agent|null = null;
|
let closestAgent: Agent|null = null;
|
||||||
for (const a of visibleAgentsInBounds) {
|
let closestDist: number = Infinity;
|
||||||
if (!closestAgent || a.pos.distance(this.pos) < closestAgent.pos.distance(this.pos)) {
|
for (const a of agents) {
|
||||||
|
const dist = a.pos.distance(this.pos);
|
||||||
|
if (!this.swarm.rect.contains(a.pos)
|
||||||
|
|| dist >= this.swarm.config.flockPerceptionDistance
|
||||||
|
|| a === this) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
visibleAgentsInBounds.push(a);
|
||||||
|
if (!closestAgent || dist < closestDist) {
|
||||||
closestAgent = a;
|
closestAgent = a;
|
||||||
|
closestDist = dist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!closestAgent) {
|
if (!closestAgent) {
|
||||||
|
|
@ -135,7 +143,7 @@ export class Agent extends GameObject {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
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);
|
const urgency = Math.max(0, (this.swarm.config.desiredDistanceToEveryone - closestDist) / this.swarm.config.desiredDistanceToEveryone);
|
||||||
return {
|
return {
|
||||||
pos: this.pos.add(awayFromClosestAgentDir),
|
pos: this.pos.add(awayFromClosestAgentDir),
|
||||||
urgency: urgency * urgency,
|
urgency: urgency * urgency,
|
||||||
|
|
@ -145,10 +153,9 @@ export class Agent extends GameObject {
|
||||||
|
|
||||||
keepFlockClose(nextPos: Vec): NavigatingDesire {
|
keepFlockClose(nextPos: Vec): NavigatingDesire {
|
||||||
const {agents} = this.swarm;
|
const {agents} = this.swarm;
|
||||||
const visibleAgents = agents
|
const visibleAgentsInBounds = agents
|
||||||
|
.filter(a => this.swarm.rect.contains(a.pos))
|
||||||
.filter(a => a.pos.distance(this.pos) < this.swarm.config.flockPerceptionDistance);
|
.filter(a => a.pos.distance(this.pos) < this.swarm.config.flockPerceptionDistance);
|
||||||
const visibleAgentsInBounds = visibleAgents
|
|
||||||
.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);
|
||||||
|
|
@ -203,17 +210,27 @@ export class Agent extends GameObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
render({ctx, input}: Canvas) {
|
render({ctx, input}: Canvas) {
|
||||||
if (input.mouseDown) {
|
if (this === this.swarm.selectedAgent) {
|
||||||
|
console.log(this.dir.clockwiseAngleBetween(1, 0));
|
||||||
|
}
|
||||||
|
if (this.swarm.config.renderStyle === 'image') {
|
||||||
|
const satisfaction = Math.floor(this.satisfaction());
|
||||||
|
const dissatisfaction = 255 - satisfaction;
|
||||||
|
// draw color
|
||||||
|
ctx.filter = `hue-rotate(${satisfaction}deg) saturate(1000%)`;
|
||||||
|
|
||||||
|
// set composite mode
|
||||||
|
drawImageRotated(ctx, schneckenImage, -this.dir.clockwiseAngleBetween(1, 0), this.pos.x, this.pos.y, imageSize, imageSize);
|
||||||
|
ctx.filter = `none`;
|
||||||
|
} else if (input.mouseDown) {
|
||||||
fillCircle(ctx, this.pos, particleSize, 'gray');
|
fillCircle(ctx, this.pos, particleSize, 'gray');
|
||||||
} else {
|
} else if (this.swarm.config.renderStyle === 'satisfaction') {
|
||||||
if (this.swarm.config.renderStyle === 'satisfaction') {
|
const satisfaction = this.satisfaction();
|
||||||
const satisfaction = this.satisfaction();
|
const dissatisfaction = 255 - satisfaction;
|
||||||
const dissatisfaction = 255 - satisfaction;
|
fillCircle(ctx, this.pos, particleSize, `rgb(${dissatisfaction}, 0, ${satisfaction})`);
|
||||||
fillCircle(ctx, this.pos, particleSize, `rgb(${dissatisfaction}, 0, ${satisfaction})`);
|
} else if (this.swarm.config.renderStyle === 'navigationMode') {
|
||||||
} else {
|
const color = navigationColorMap[this.lastNavigation.type];
|
||||||
const color = navigationColorMap[this.lastNavigation.type];
|
fillCircle(ctx, this.pos, particleSize, color);
|
||||||
fillCircle(ctx, this.pos, particleSize, color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (this.swarm.config.showDirections) {
|
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)));
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { Agent } from "@/game/swarm/agent";
|
||||||
import { strokeCircle } from "@/graphics";
|
import { strokeCircle } from "@/graphics";
|
||||||
import Button from "@/ui/button";
|
import Button from "@/ui/button";
|
||||||
|
|
||||||
export type RenderStyle = 'satisfaction'|'navigationMode';
|
export type RenderStyle = 'satisfaction'|'navigationMode'|'image';
|
||||||
|
|
||||||
export class Config {
|
export class Config {
|
||||||
numAgents: number = 20;
|
numAgents: number = 20;
|
||||||
|
|
@ -15,7 +15,7 @@ export class Config {
|
||||||
idlingUrgency: number = 0.001;
|
idlingUrgency: number = 0.001;
|
||||||
idlingDistance: number = 20;
|
idlingDistance: number = 20;
|
||||||
desiredDistanceToEveryone: number = 50;
|
desiredDistanceToEveryone: number = 50;
|
||||||
renderStyle: RenderStyle = 'satisfaction';
|
renderStyle: RenderStyle = 'image';
|
||||||
showDirections: boolean = false;
|
showDirections: boolean = false;
|
||||||
showFlockCenter: boolean = false;
|
showFlockCenter: boolean = false;
|
||||||
flockPerceptionDistance: number = 100;
|
flockPerceptionDistance: number = 100;
|
||||||
|
|
@ -64,15 +64,23 @@ export class Swarm extends GameObject {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
new Button("Reset", Rect.bySize(new Vec(10, 10), buttonSize), () => this.init(canvas)),
|
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.config.renderStyle = this.config.renderStyle === 'satisfaction' ? 'navigationMode' : 'satisfaction';
|
const styles: RenderStyle[] = ['satisfaction', 'navigationMode', 'image'];
|
||||||
|
this.config.renderStyle = styles[(styles.indexOf(this.config.renderStyle) + 1) % styles.length];
|
||||||
}),
|
}),
|
||||||
new Button("Less Agents", Rect.bySize(new Vec(10, 60), buttonSize), () => {
|
new Button("Less Agents", Rect.bySize(new Vec(10, 60), buttonSize), () => {
|
||||||
this.config.numAgents -= Math.max(1, this.config.numAgents / 10);
|
const agentsToRemove = Math.max(1, this.config.numAgents / 10);
|
||||||
this.init(canvas);
|
this.agents.splice(-agentsToRemove, agentsToRemove).forEach(a => canvas.remove(a));
|
||||||
|
this.config.numAgents -= agentsToRemove;
|
||||||
}),
|
}),
|
||||||
new Button("More Agents", Rect.bySize(new Vec(110, 60), buttonSize), () => {
|
new Button("More Agents", Rect.bySize(new Vec(110, 60), buttonSize), () => {
|
||||||
this.config.numAgents += Math.max(1, this.config.numAgents / 10);
|
const agentsToAdd = Math.max(1, this.config.numAgents / 10);
|
||||||
this.init(canvas);
|
for (let i = 0; i < agentsToAdd; i++) {
|
||||||
|
const pos = Vec.random(this.rect);
|
||||||
|
const agent = new Agent(pos, this);
|
||||||
|
this.agents.push(agent);
|
||||||
|
canvas.add(agent);
|
||||||
|
}
|
||||||
|
this.config.numAgents += agentsToAdd;
|
||||||
}),
|
}),
|
||||||
new Button("toggle Directions", Rect.bySize(new Vec(10, 110), buttonSize), () => {
|
new Button("toggle Directions", Rect.bySize(new Vec(10, 110), buttonSize), () => {
|
||||||
this.config.showDirections = !this.config.showDirections;
|
this.config.showDirections = !this.config.showDirections;
|
||||||
|
|
@ -101,6 +109,11 @@ export class Swarm extends GameObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
update(canvas: Canvas, delta: DOMHighResTimeStamp) {
|
update(canvas: Canvas, delta: DOMHighResTimeStamp) {
|
||||||
|
if (canvas.input.keypressed('Escape')) {
|
||||||
|
canvas.camera.reset();
|
||||||
|
canvas.camera.unlock();
|
||||||
|
this.selectedAgent = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render(canvas: Canvas) {
|
render(canvas: Canvas) {
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,12 @@ export function drawImageRotated(
|
||||||
y: number,
|
y: number,
|
||||||
w: number,
|
w: number,
|
||||||
h: number,
|
h: number,
|
||||||
x2: number,
|
x2?: number,
|
||||||
y2: number,
|
y2?: number,
|
||||||
w2: number,
|
w2?: number,
|
||||||
h2: number,
|
h2?: number,
|
||||||
) {
|
) {
|
||||||
if (typeof h2 !== "undefined") {
|
if (typeof x2 !== "undefined" && typeof h2 !== "undefined" && typeof w2 !== "undefined" && typeof y2 !== "undefined") {
|
||||||
ctx.translate(x2, y2);
|
ctx.translate(x2, y2);
|
||||||
ctx.rotate(angle);
|
ctx.rotate(angle);
|
||||||
ctx.drawImage(img, x, y, w, h, -w2 / 2, -h2 / 2, w2, h2);
|
ctx.drawImage(img, x, y, w, h, -w2 / 2, -h2 / 2, w2, h2);
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,13 @@ const config = {
|
||||||
use: [stylesHandler, "css-loader"],
|
use: [stylesHandler, "css-loader"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
|
test: /\.(eot|svg|ttf|woff|woff2|jpg|gif)$/i,
|
||||||
type: "asset",
|
type: "asset",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png)$/i,
|
||||||
|
type: "asset/inline",
|
||||||
|
},
|
||||||
|
|
||||||
// Add your rules for custom modules here
|
// Add your rules for custom modules here
|
||||||
// Learn more about loaders from https://webpack.js.org/loaders/
|
// Learn more about loaders from https://webpack.js.org/loaders/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user