Files
EVEngine/test/crowd.nut
T
西风逍遥游 9d5e61fde8 feat(crowd): flow-field pathfinding + boids crowd behavior module
- CrowdField: Dijkstra integration field to continuous gradient flow
  field with bilinear sampling, terrain cost and multi-goal support
- Crowd: SOA simulation for massive crowds - flow/seek/boids/idle
  actions, turn-rate-limited heading, Boids separation/alignment/
  cohesion, wall penetration resolution, per-frame counting-sort
  spatial grid and overlap relaxation
- Squirrel API: eve.Crowd with batch getPositions/getHeadings
- example: examples/crowd (2000-agent marching / boids demo)
- tests: flow field correctness, turning, seek arrival, boids
  convergence, wall penetration, 20k-agent smoke + script round-trip
2026-08-23 19:17:12 +08:00

105 lines
3.0 KiB
Plaintext

function basic() {
if (eve.Crowd == null) {
print("crowd module not built\n");
return false;
}
local crowd = eve.Crowd();
if (crowd.getName() != "Crowd") {
print("Crowd name mismatch\n");
return false;
}
// ---- 流场 ----
crowd.resizeField(16, 16, 16.0, 0.0, 0.0);
crowd.setBlocked(8, 3, true);
crowd.setCellCost(8, 4, 5.0);
crowd.buildFlowField(15, 15);
if (!crowd.isFieldBuilt()) {
print("field not built\n");
return false;
}
if (!crowd.isReachable(0, 0)) {
print("start unreachable\n");
return false;
}
local f = crowd.flowAtWorld(10.0, 10.0);
if (f.x * f.x + f.y * f.y < 0.5) {
print("flow not unit length\n");
return false;
}
if (crowd.costAtWorld(10.0, 10.0) <= 0.0) {
print("bad cost\n");
return false;
}
// ---- 单位 ----
local id = crowd.addAgent(16.0, 16.0, 0.0, 5.0);
if (id < 0) {
print("addAgent failed\n");
return false;
}
crowd.setAgentAction(id, "seek");
crowd.setAgentTarget(id, 200.0, 200.0);
crowd.setAgentSpeed(id, 100.0);
crowd.setAgentTurnRate(id, 3.0);
crowd.setAgentData(id, 42);
if (crowd.getAgentAction(id) != "seek") {
print("action mismatch\n");
return false;
}
local s = crowd.getAgentState(id);
if (s.action != 2 || s.data != 42 || s.x != 16.0 || s.y != 16.0) {
print("state mismatch\n");
return false;
}
crowd.step(0.016);
if (crowd.getAgentCount() != 1) {
print("count mismatch\n");
return false;
}
// ---- 批量读取 ----
local xs = array(crowd.getAgentCount());
local ys = array(crowd.getAgentCount());
local hs = array(crowd.getAgentCount());
crowd.getPositions(xs, ys);
crowd.getHeadings(hs);
if (xs.len() != 1 || ys.len() != 1 || hs.len() != 1 || xs[0] == null) {
print("bulk arrays mismatch\n");
return false;
}
// ---- Boids 参数 + 推进 ----
crowd.setAgentAction(id, "boids");
crowd.setSeparationRadius(24.0);
crowd.setPerceptionRadius(48.0);
crowd.setSeparationWeight(1.0);
crowd.setAlignmentWeight(0.5);
crowd.setCohesionWeight(0.3);
crowd.setWanderWeight(0.1);
for (local i = 0; i < 30; i += 1) crowd.step(0.016);
if (crowd.removeAgent(id) != true) {
print("remove failed\n");
return false;
}
if (crowd.getAgentCount() != 0) {
print("count after remove mismatch\n");
return false;
}
return true;
}
function invalidIds() {
if (eve.Crowd == null) return true;
local crowd = eve.Crowd();
crowd.clearAgents();
if (crowd.getAgentState(999).action != -1) return false;
if (crowd.removeAgent(999) != false) return false;
if (crowd.setAgentSpeed(999, 10.0) != false) return false;
if (crowd.setAgentAction(999, "seek") != false) return false;
if (crowd.getAgentAction(999) != "") return false;
return true;
}