Hi everyone,
I tried to simulate boids in 3D space and it kinda works, but also not (?).
My specific problem here is, that the boids behave a bit weird, if it comes to border/edge behaviour.
So I already specified the behaviour to spawn on the other side of the defined values back if it hits a specific spot in the coordinate system. So it actually works, they spawn on the other side and continue on flying.
But weirdly, it happens that there is one boid (?) in (0, 0, 0) that does not move and it looks a bit like as most of the boids spawn out of this spot (but they actually don’t if you have a closer look).
Does anyone know why this happens? I am really clueless and tried already a lot.
!!! Also if I try other conditions for the if-statements, there are other weird things happening around the (0, 0, 0). What’s wrong with this (0, 0, 0)?
Thanks in advance!
The JS File:
23.04.2024-test.js (8.8 KB)
The HTML:
index.html (310 Bytes)
The JS Code:
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { TextureLoader } from 'three';
const flock = [];
// texture
// Erstellung der Szene + Kamera
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
const light = new THREE.PointLight(0xffffff, 1);
light.position.set(800, 300, 1000);
scene.add(light);
// Renderer, damit die Szene auch gezeigt wird
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
const controls = new OrbitControls( camera, renderer.domElement );
// BG Farbe hier ändern
scene.background = new THREE.Color(808080);
// fog
scene.fog = new THREE.Fog( 0xcccccc, 10, 15 );
// Kamera Position ändern
camera.position.set(800, 300, 1000);
camera.lookAt(60, 710, 10)
controls.update();
// Laden des Boid-Modells aus der Datei
async function loadBoidModel() {
const loader = new GLTFLoader();
return new Promise((resolve, reject) => {
loader.load('3d_models/low_poly_bird/scene.gltf', gltf => {
resolve(gltf.scene); // Lade die ganze Szene als Mesh
}, undefined, reject);
});
}
// Verfolgung der Mausposition in der Szene
const mouse = new THREE.Vector2();
function onMouseMove(event) {
// Normalisiere die Mausposition auf das Fenster, um Werte zwischen -1 und 1 zu erhalten
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
// Eventlistener für Mausbewegung
window.addEventListener('mousemove', onMouseMove, false);
// Boid-Klasse
class Boid {
constructor(){
this.position = new THREE.Vector3(this.getRandomIntInclusive(0, 2000), this.getRandomIntInclusive(0, 2000), this.getRandomIntInclusive(0, 2000));
this.velocity = new THREE.Vector3();
this.velocity.randomDirection();
this.velocity.setLength(this.getRandomIntInclusive(2, 4));
this.acceleration = new THREE.Vector3();
this.maxForce = 0.1;
this.maxSpeed = 5;
this.xBoundaries = 2000;
this.yBoundaries = 2000;
this.zBoundaries = 2000;
this.boidMesh = null;
this.createBoidMesh(); // Erstellung eines Boids
}
// Methode zur Anpassung der Bewegung basierend auf der Mausposition
steerAwayFromMouse() {
const strength = 0.5; // Stärke der Abstoßung vom Cursor
const target = new THREE.Vector3(mouse.x, mouse.y, 0); // Ziel ist die Mausposition auf der Ebene z=0
// Berechne die Richtung und den Abstand zwischen dem Boid und der Maus
const direction = new THREE.Vector3().subVectors(this.position, target);
const distance = direction.length();
// Wenn die Maus nahe genug ist, weiche dem Cursor aus
if (distance < 100) {
direction.normalize().multiplyScalar(strength);
this.acceleration.add(direction);
}
}
async createBoidMesh() {
const boidModel = await loadBoidModel();
this.boidMesh = boidModel.clone(); // Klone das geladene Modell
this.boidMesh.scale.set(10, 10, 10); // Skalierung des Modells
scene.add(this.boidMesh); // Füge das Modell der Szene hinzu
await loadTexturesAndApplyToModel(this.boidMesh); // Lade Texturen und wende sie auf das Modell an
}
getRandomIntInclusive(min, max) {
const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max);
return Math.floor(Math.random() * (maxFloored - minCeiled + 1) + minCeiled); // The maximum is inclusive and the minimum is inclusive
}
edges(){
/* this.position.x = this.boundaries(this.position.x, this.xBoundaries);
this.position.y = this.boundaries(this.position.y, this.yBoundaries);
this.position.z = this.boundaries(this.position.z, this.zBoundaries); */
if (this.position.x > this.xBoundaries){
this.position.x = 0;
} else if(this.position.x < 0){
this.position.x = this.xBoundaries;
}
if (this.position.y > this.yBoundaries){
this.position.y = 0;
} else if(this.position.y < 0){
this.position.y = this.yBoundaries;
}
if (this.position.z > this.zBoundaries){
this.position.z = 0;
} else if(this.position.z < 0){
this.position.z = this.zBoundaries;
}
// Überprüfen und umkehren, wenn die Boids die Grenzen erreichen
/* if (this.position.x > this.xBoundaries || this.position.x < 0) {
this.velocity.x *= -1;
}
if (this.position.y > this.yBoundaries || this.position.y < 0) {
this.velocity.y *= -1;
}
if (this.position.z > this.zBoundaries || this.position.z < 0) {
this.velocity.z *= -1;
} */
}
/* boundaries(thisPosition, boundaries){
if (thisPosition > boundaries) {
thisPosition = 0;
} else if (thisPosition < 0) {
thisPosition = boundaries;
}
return thisPosition;
}
*/
align(boids) {
let perceptionRadius = 50;
let steering = new THREE.Vector3();
let total = 0;
for (let other of boids) {
if (other != this && this.calculateDistance(boids, this.position, other.position) < perceptionRadius) {
steering.add(other.velocity);
total++;
}
}
if (total > 0) {
steering.divideScalar(total);
steering.setLength(this.maxSpeed);
steering.sub(this.velocity);
steering.clampScalar(0, this.maxForce);
}
return steering;
}
cohesion(boids){
let perceptionRadius = 70;
let steering = new THREE.Vector3();
let total = 0;
for (let other of boids) {
if (other != this && this.calculateDistance(boids, this.position, other.position) < perceptionRadius) {
steering.add(other.position);
total++;
}
}
if (total > 0) {
steering.divideScalar(total);
steering.sub(this.position);
steering.setLength(this.maxSpeed);
steering.sub(this.velocity);
steering.clampScalar(0, this.maxForce);
}
return steering;
}
separation(boids){
let perceptionRadius = 30;
let steering = new THREE.Vector3();
let total = 0;
for (let other of boids) {
let distance = this.calculateDistance(boids, this.position, other.position);
if (other != this && distance < perceptionRadius) {
let diff = other.position.sub(this.position);
diff.divideScalar(distance * distance);
steering.add(diff);
total++;
}
}
if (total > 0) {
steering.divideScalar(total);
steering.setLength(this.maxSpeed);
steering.sub(this.velocity);
steering.clampScalar(0, this.maxForce);
}
return steering;
}
calculateDistance(boids, position, otherPosition){
for(let other of boids){
let distance = position.distanceTo(otherPosition);
return distance;
}
}
flock(boids){
let alignment = this.align(boids);
let cohesion = this.cohesion(boids);
let separation = this.separation(boids);
this.acceleration.add(alignment);
this.acceleration.add(cohesion);
this.acceleration.add(separation);
}
update(){
this.position.add(this.velocity);
this.velocity.add(this.acceleration);
this.velocity.clampLength(0, this.maxSpeed);
this.acceleration.multiplyScalar(0);
}
show () {
if (this.boidMesh) {
this.boidMesh.position.copy(this.position);
this.boidMesh.lookAt(this.position.clone().add(this.velocity)); // Richtung des Boids ausrichten
}
}
}
// Erstellung der Boids
for (let i = 0; i < 300; i++) {
flock.push(new Boid());
}
// Eventlistener für Orbit-Steuerung
controls.addEventListener( "change", event => {
console.log( controls.object.position );
});
const size = 1000;
const divisions = 100;
const gridHelper = new THREE.GridHelper( size, divisions );
gridHelper.position.set(0, 0, 0);
scene.add( gridHelper );
// Animationsloop
function animate() {
requestAnimationFrame(animate);
for (let boid of flock) {
boid.edges();
boid.flock(flock);
boid.update();
boid.show();
}
controls.update();
renderer.render(scene, camera);
}
// Starte die Animation
animate();
If I change the edge()-function to this:
if (this.position.x > this.xBoundaries || this.position.x < 0) {
this.velocity.x *= -1; // Invertiere die x-Richtung
this.position.x = Math.min(Math.max(this.position.x, 0), this.xBoundaries);
}
if (this.position.y > this.yBoundaries || this.position.y < 0) {
this.velocity.y *= -1; // Invertiere die y-Richtung
this.position.y = Math.min(Math.max(this.position.y, 0), this.yBoundaries);
}
if (this.position.z > this.zBoundaries || this.position.z < 0) {
this.velocity.z *= -1; // Invertiere die z-Richtung
this.position.z = Math.min(Math.max(this.position.z, 0), this.zBoundaries);
}
Then it looks like this:


