Make some schnecken

This commit is contained in:
Schmop 2024-05-16 02:37:57 +02:00
parent f205d23179
commit 0eed2881ad
8 changed files with 74 additions and 38 deletions

BIN
src/assets/schnecke.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -164,13 +164,13 @@ export class Vec {
distance(x: number, y: number): number;
distance(x: number|Vec, y?: number): number {
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) {
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) {

1
src/env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module "*.png";

View File

@ -9,6 +9,7 @@ const gravity = 0.008;
const numParticles = 200;
const pressureForce = 8000;
export const particleSize = 10;
export const imageSize = 64;
export const desiredDensity = 6;
const densityRadius = 40;
const mass = 1;

View File

@ -1,11 +1,13 @@
import { Canvas } from "@/canvas";
import { GameObject } from "@/common/gameobject";
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 { 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 NavigatingDesire = {
pos: Vec;
@ -117,14 +119,20 @@ export class Agent extends GameObject {
doNotBumpIntoFlock(nextPos: Vec): NavigatingDesire {
const {agents} = this.swarm;
const visibleAgentsNotMe = agents
.filter(a => a.pos.distance(this.pos) < this.swarm.config.flockPerceptionDistance && a !== this);
const visibleAgentsInBounds = visibleAgentsNotMe
.filter(a => this.swarm.rect.contains(a.pos));
const visibleAgentsInBounds = [];
let closestAgent: Agent|null = null;
for (const a of visibleAgentsInBounds) {
if (!closestAgent || a.pos.distance(this.pos) < closestAgent.pos.distance(this.pos)) {
let closestDist: number = Infinity;
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;
closestDist = dist;
}
}
if (!closestAgent) {
@ -135,7 +143,7 @@ export class Agent extends GameObject {
};
}
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 {
pos: this.pos.add(awayFromClosestAgentDir),
urgency: urgency * urgency,
@ -145,10 +153,9 @@ export class Agent extends GameObject {
keepFlockClose(nextPos: Vec): NavigatingDesire {
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);
const visibleAgentsInBounds = visibleAgents
.filter(a => this.swarm.rect.contains(a.pos));
const center = visibleAgentsInBounds
.reduce((sum, a) => sum.add(a.pos), new Vec)
.scale(1 / visibleAgentsInBounds.length);
@ -203,17 +210,27 @@ export class Agent extends GameObject {
}
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');
} else {
if (this.swarm.config.renderStyle === 'satisfaction') {
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);
}
} else if (this.swarm.config.renderStyle === 'satisfaction') {
const satisfaction = this.satisfaction();
const dissatisfaction = 255 - satisfaction;
fillCircle(ctx, this.pos, particleSize, `rgb(${dissatisfaction}, 0, ${satisfaction})`);
} else if (this.swarm.config.renderStyle === 'navigationMode') {
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)));

View File

@ -6,7 +6,7 @@ import { Agent } from "@/game/swarm/agent";
import { strokeCircle } from "@/graphics";
import Button from "@/ui/button";
export type RenderStyle = 'satisfaction'|'navigationMode';
export type RenderStyle = 'satisfaction'|'navigationMode'|'image';
export class Config {
numAgents: number = 20;
@ -15,7 +15,7 @@ export class Config {
idlingUrgency: number = 0.001;
idlingDistance: number = 20;
desiredDistanceToEveryone: number = 50;
renderStyle: RenderStyle = 'satisfaction';
renderStyle: RenderStyle = 'image';
showDirections: boolean = false;
showFlockCenter: boolean = false;
flockPerceptionDistance: number = 100;
@ -64,15 +64,23 @@ export class Swarm extends GameObject {
const buttons = [
new Button("Reset", Rect.bySize(new Vec(10, 10), buttonSize), () => this.init(canvas)),
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), () => {
this.config.numAgents -= Math.max(1, this.config.numAgents / 10);
this.init(canvas);
const agentsToRemove = Math.max(1, this.config.numAgents / 10);
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), () => {
this.config.numAgents += Math.max(1, this.config.numAgents / 10);
this.init(canvas);
const agentsToAdd = Math.max(1, this.config.numAgents / 10);
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), () => {
this.config.showDirections = !this.config.showDirections;
@ -101,6 +109,11 @@ export class Swarm extends GameObject {
}
update(canvas: Canvas, delta: DOMHighResTimeStamp) {
if (canvas.input.keypressed('Escape')) {
canvas.camera.reset();
canvas.camera.unlock();
this.selectedAgent = null;
}
}
render(canvas: Canvas) {

View File

@ -9,12 +9,12 @@ export function drawImageRotated(
y: number,
w: number,
h: number,
x2: number,
y2: number,
w2: number,
h2: number,
x2?: number,
y2?: number,
w2?: 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.rotate(angle);
ctx.drawImage(img, x, y, w, h, -w2 / 2, -h2 / 2, w2, h2);

View File

@ -36,9 +36,13 @@ const config = {
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",
},
{
test: /\.(png)$/i,
type: "asset/inline",
},
// Add your rules for custom modules here
// Learn more about loaders from https://webpack.js.org/loaders/