/** * 返回可指定大小的随机二维向量 * @param {number} magnitude 向量大小 * @return {p5.Vector} vector 随机二维向量 */ function randomVector(magnitude) { let vector = p5.Vector.random2D(); return magnitude ? vector.mult(magnitude) : vector; } class Vehicle { constructor(x, y, color) { this.path = []; this.color = color; this.position = createVector(x, y); this.velocity = createVector(0, 0); this.heading = this.velocity.heading(); this.acceleration = createVector(0, 0); this.maxVelocity = 6; this.maxAcceleration = 0.25; } seek(target, slowdownDistance = 100) { const desired = p5.Vector.sub(target, this.position); const distance = desired.mag(); const magnitude = slowdownDistance > 0 && distance <= slowdownDistance ? map(distance, 0, slowdownDistance, 0, this.maxVelocity) : this.maxVelocity; desired.setMag(magnitude); const steering = p5.Vector.sub(desired, this.velocity); this.acceleration.add(steering); } flee(target, fleeDistance = 100) { const desired = p5.Vector.sub(this.position, target); const distance = desired.mag(); const magnitude = !fleeDistance || fleeDistance <= 0 ? this.maxVelocity : distance <= fleeDistance ? map(distance, 0, fleeDistance, this.maxVelocity, 0) : 0; desired.setMag(magnitude); const steering = p5.Vector.sub(desired, this.velocity); this.acceleration.add(steering); } wander() { const target = this.velocity.copy(); target.setMag(50).add(this.position); // stroke(128); // const x1 = target.x + -25 * cos(PI / 2 + this.heading); // const y1 = target.y + -25 * sin(PI / 2 + this.heading); // const x2 = target.x + 25 * cos(PI / 2 + this.heading); // const y2 = target.y + 25 * sin(PI / 2 + this.heading); // line(x1, y1, x2, y2); const offset = map(noise(Date.now() / 1000), 0, 1, -50, 50); const x = offset * cos(PI / 2 + this.heading); const y = offset * sin(PI / 2 + this.heading); target.add(x, y); noStroke(); fill("#F063A4"); circle(target.x, target.y, 5); const steering = target.sub(this.position); this.acceleration.add(steering); } follow(path, ahead = 50) { // find future point of vehicle const future = p5.Vector.add( this.position, this.velocity.copy().mult(ahead) ); // noStroke(); // fill("#FF0000"); // circle(future.x, future.y, 5); // find project point on the path const v1 = p5.Vector.sub(future, path.start); const v2 = p5.Vector.sub(path.end, path.start); const project = path.start.copy().add(vectorProjection(v1, v2)); noStroke(); fill("#00FF00"); circle(project.x, project.y, 5); // find the distance between project point and future point const distance = future.dist(project); // if the distance larger than path width then seek the project point if (distance > path.width) this.seek(project, -1); } move() { this.acceleration.limit(this.maxAcceleration); this.velocity.add(this.acceleration); if (this.velocity.mag() > 0) this.heading = this.velocity.heading(); this.velocity.limit(this.maxVelocity); this.position.add(this.velocity); this.acceleration.set(0, 0); this.path.push(this.position.copy()); // this.acceleration.limit(this.maxAcceleration); // // 计算时间差 // const duration = deltaTime / 1000; // // 计算速度及位移 // const deltaVelocity = this.acceleration.mult(duration); // const averageVelocity = deltaVelocity.copy().div(2).add(this.velocity); // const movement = averageVelocity.mult(duration); // // 更新速度及位置 // this.velocity.add(deltaVelocity); // this.velocity.limit(this.maxVelocity); // if (this.velocity.mag() > 0) this.heading = this.velocity.heading(); // this.position.add(movement); // this.acceleration.set(0, 0); // this.path.push(this.position.copy()); } turn() { let x = this.position.x; let y = this.position.y; while (x < 0 || x >= width) x = (x + width) % width; while (y < 0 || y >= height) y = (y + height) % height; const edgeCrossed = x != this.position.x || y != this.position.y; this.position.x = x; this.position.y = y; if (edgeCrossed) { this.path.pop(); this.path.push(null); } return edgeCrossed; } // bounce() { // if (this.position.x <= this.size / 2) { // this.position.x = this.size / 2; // this.velocity.x *= -1; // return true; // } else if (this.position.x > canvasWidth - this.size / 2) { // this.position.x = canvasWidth - this.size / 2; // this.velocity.x *= -1; // return true; // } // if (this.position.y <= this.size / 2) { // this.position.y = this.size / 2; // this.velocity.y *= -1; // return true; // } else if (this.position.y >= canvasHeight - this.size / 2) { // this.position.y = canvasHeight - this.size / 2; // this.velocity.y *= -1; // return true; // } // return false; // } drawPath(length = 1024) { push(); noFill(); stroke(255, 64); strokeWeight(1); if (this.path.length >= length) this.path.shift(); beginShape(); this.path.forEach((v) => { if (!v) { endShape(); beginShape(); } else { vertex(v.x, v.y); } }); endShape(); pop(); } show(mode = "triangle") { push(); translate(this.position.x, this.position.y); rotate(this.heading); stroke(this.color); fill(this.color); if (mode == "point") circle(0, 0, 5); else triangle(0, 0, -10, 2.5, -10, -2.5); pop(); } } class Path { constructor(x1, y1, x2, y2, width = 20) { this.start = createVector(x1, y1); this.end = createVector(x2, y2); this.width = width; } show() { push(); stroke(255, 64); strokeWeight(this.width * 2); line(this.start.x, this.start.y, this.end.x, this.end.y); stroke(255); strokeWeight(1); line(this.start.x, this.start.y, this.end.x, this.end.y); pop(); } } function vectorProjection(v1, v2) { const v2n = v2.copy().normalize(); const sp = v1.dot(v2n); return v2n.setMag(sp); }