123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
- import {game} from './game.js';
- import {math} from './math.js';
- import {visibility} from './visibility.js';
- import {OBJLoader} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/OBJLoader.js';
- let _APP = null;
- const _NUM_BOIDS = 350;
- const _BOID_SPEED = 2.5;
- const _BOID_ACCELERATION = _BOID_SPEED / 5.0;
- const _BOID_FORCE_MAX = _BOID_ACCELERATION / 10.0;
- const _BOID_FORCE_ORIGIN = 8;
- const _BOID_FORCE_ALIGNMENT = 10;
- const _BOID_FORCE_SEPARATION = 20;
- const _BOID_FORCE_COHESION = 10;
- const _BOID_FORCE_WANDER = 3;
- class LineRenderer {
- constructor(game) {
- this._game = game;
- this._materials = {};
- this._group = new THREE.Group();
- this._game._graphics.Scene.add(this._group);
- }
- Reset() {
- this._lines = [];
- this._group.remove(...this._group.children);
- }
- Add(pt1, pt2, hexColour) {
- const geometry = new THREE.Geometry();
- geometry.vertices.push(pt1);
- geometry.vertices.push(pt2);
- let material = this._materials[hexColour];
- if (!material) {
- this._materials[hexColour] = new THREE.LineBasicMaterial(
- {color: hexColour});
- material = this._materials[hexColour];
- }
- const line = new THREE.Line(geometry, material);
- this._lines.push(line);
- this._group.add(line);
- }
- }
- class Boid {
- constructor(game, params) {
- this._mesh = new THREE.Mesh(
- params.geometry,
- new THREE.MeshStandardMaterial({color: params.colour}));
- this._mesh.castShadow = true;
- this._mesh.receiveShadow = false;
- this._group = new THREE.Group();
- this._group.add(this._mesh);
- this._group.position.set(
- math.rand_range(-50, 50),
- math.rand_range(1, 25),
- math.rand_range(-50, 50));
- this._direction = new THREE.Vector3(
- math.rand_range(-1, 1),
- 0,
- math.rand_range(-1, 1));
- this._velocity = this._direction.clone();
- const speedMultiplier = math.rand_range(params.speedMin, params.speedMax);
- this._maxSteeringForce = params.maxSteeringForce * speedMultiplier;
- this._maxSpeed = params.speed * speedMultiplier;
- this._acceleration = params.acceleration * speedMultiplier;
- const scale = 1.0 / speedMultiplier;
- this._radius = scale;
- this._mesh.scale.setScalar(scale);
- this._mesh.rotateX(-Math.PI / 2);
- this._game = game;
- game._graphics.Scene.add(this._group);
- this._visibilityIndex = game._visibilityGrid.UpdateItem(
- this._mesh.uuid, this);
- this._wanderAngle = 0;
- }
- DisplayDebug() {
- const geometry = new THREE.SphereGeometry(10, 64, 64);
- const material = new THREE.MeshBasicMaterial({
- color: 0xFF0000,
- transparent: true,
- opacity: 0.25,
- });
- const mesh = new THREE.Mesh(geometry, material);
- this._group.add(mesh);
- this._mesh.material.color.setHex(0xFF0000);
- this._displayDebug = true;
- this._lineRenderer = new LineRenderer(this._game);
- }
- _UpdateDebug(local) {
- this._lineRenderer.Reset();
- this._lineRenderer.Add(
- this.Position, this.Position.clone().add(this._velocity),
- 0xFFFFFF);
- for (const e of local) {
- this._lineRenderer.Add(this.Position, e.Position, 0x00FF00);
- }
- }
- get Position() {
- return this._group.position;
- }
- get Velocity() {
- return this._velocity;
- }
- get Direction() {
- return this._direction;
- }
- get Radius() {
- return this._radius;
- }
- Step(timeInSeconds) {
- if (this._displayDebug) {
- let a = 0;
- }
- const local = this._game._visibilityGrid.GetLocalEntities(
- this.Position, 15);
- this._ApplySteering(timeInSeconds, local);
- const frameVelocity = this._velocity.clone();
- frameVelocity.multiplyScalar(timeInSeconds);
- this._group.position.add(frameVelocity);
- const direction = this.Direction;
- const m = new THREE.Matrix4();
- m.lookAt(
- new THREE.Vector3(0, 0, 0),
- direction,
- new THREE.Vector3(0, 1, 0));
- this._group.quaternion.setFromRotationMatrix(m);
- this._visibilityIndex = this._game._visibilityGrid.UpdateItem(
- this._mesh.uuid, this, this._visibilityIndex);
- if (this._displayDebug) {
- this._UpdateDebug(local);
- }
- }
- _ApplySteering(timeInSeconds, local) {
- const forces = [
- this._ApplySeek(new THREE.Vector3(0, 10, 0)),
- this._ApplyWander(),
- this._ApplyGroundAvoidance(),
- this._ApplySeparation(local),
- ];
- if (this._radius < 5) {
- // Only apply alignment and cohesion to similar sized fish.
- local = local.filter((e) => {
- const ratio = this.Radius / e.Radius;
- return (ratio <= 1.35 && ratio >= 0.75);
- });
- forces.push(
- this._ApplyAlignment(local),
- this._ApplyCohesion(local),
- this._ApplySeparation(local)
- )
- }
- const steeringForce = new THREE.Vector3(0, 0, 0);
- for (const f of forces) {
- steeringForce.add(f);
- }
- steeringForce.multiplyScalar(this._acceleration * timeInSeconds);
- // Preferentially move in x/z dimension
- steeringForce.multiply(new THREE.Vector3(1, 0.25, 1));
- // Clamp the force applied
- if (steeringForce.length() > this._maxSteeringForce) {
- steeringForce.normalize();
- steeringForce.multiplyScalar(this._maxSteeringForce);
- }
- this._velocity.add(steeringForce);
- // Clamp velocity
- if (this._velocity.length() > this._maxSpeed) {
- this._velocity.normalize();
- this._velocity.multiplyScalar(this._maxSpeed);
- }
- this._direction = this._velocity.clone();
- this._direction.normalize();
- }
- _ApplyGroundAvoidance() {
- const p = this.Position;
- let force = new THREE.Vector3(0, 0, 0);
- if (p.y < 10) {
- force = new THREE.Vector3(0, 10 - p.y, 0);
- } else if (p.y > 30) {
- force = new THREE.Vector3(0, p.y - 50, 0);
- }
- return force.multiplyScalar(_BOID_FORCE_SEPARATION);
- }
- _ApplyWander() {
- this._wanderAngle += 0.1 * math.rand_range(-2 * Math.PI, 2 * Math.PI);
- const randomPointOnCircle = new THREE.Vector3(
- Math.cos(this._wanderAngle),
- 0,
- Math.sin(this._wanderAngle));
- const pointAhead = this._direction.clone();
- pointAhead.multiplyScalar(2);
- pointAhead.add(randomPointOnCircle);
- pointAhead.normalize();
- return pointAhead.multiplyScalar(_BOID_FORCE_WANDER);
- }
- _ApplySeparation(local) {
- if (local.length == 0) {
- return new THREE.Vector3(0, 0, 0);
- }
- const forceVector = new THREE.Vector3(0, 0, 0);
- for (let e of local) {
- const distanceToEntity = Math.max(
- e.Position.distanceTo(this.Position) - 1.5 * (this.Radius + e.Radius),
- 0.001);
- const directionFromEntity = new THREE.Vector3().subVectors(
- this.Position, e.Position);
- const multiplier = (
- _BOID_FORCE_SEPARATION / distanceToEntity) * (this.Radius + e.Radius);
- directionFromEntity.normalize();
- forceVector.add(
- directionFromEntity.multiplyScalar(multiplier));
- }
- return forceVector;
- }
- _ApplyAlignment(local) {
- const forceVector = new THREE.Vector3(0, 0, 0);
- for (let e of local) {
- const entityDirection = e.Direction;
- forceVector.add(entityDirection);
- }
- forceVector.normalize();
- forceVector.multiplyScalar(_BOID_FORCE_ALIGNMENT);
- return forceVector;
- }
- _ApplyCohesion(local) {
- const forceVector = new THREE.Vector3(0, 0, 0);
- if (local.length == 0) {
- return forceVector;
- }
- const averagePosition = new THREE.Vector3(0, 0, 0);
- for (let e of local) {
- averagePosition.add(e.Position);
- }
- averagePosition.multiplyScalar(1.0 / local.length);
- const directionToAveragePosition = averagePosition.clone().sub(
- this.Position);
- directionToAveragePosition.normalize();
- directionToAveragePosition.multiplyScalar(_BOID_FORCE_COHESION);
- return directionToAveragePosition;
- }
- _ApplySeek(destination) {
- const distance = Math.max(0,((
- this.Position.distanceTo(destination) - 50) / 250)) ** 2;
- const direction = destination.clone().sub(this.Position);
- direction.normalize();
- const forceVector = direction.multiplyScalar(
- _BOID_FORCE_ORIGIN * distance);
- return forceVector;
- }
- }
- class FishDemo extends game.Game {
- constructor() {
- super();
- }
- _OnInitialize() {
- this._entities = [];
- this._graphics.Scene.fog = new THREE.FogExp2(
- new THREE.Color(0x4d7dbe), 0.01);
- this._LoadBackground();
- const loader = new OBJLoader();
- const geoLibrary = {};
- loader.load("./resources/fish.obj", (result) => {
- geoLibrary.fish = result.children[0].geometry;
- loader.load("./resources/bigfish.obj", (result) => {
- geoLibrary.bigFish = result.children[0].geometry;
- this._CreateBoids(geoLibrary);
- });
- });
- this._CreateEntities();
- }
- _LoadBackground() {
- const loader = new THREE.TextureLoader();
- const texture = loader.load('./resources/underwater.jpg');
- this._graphics._scene.background = texture;
- }
- _CreateEntities() {
- const plane = new THREE.Mesh(
- new THREE.PlaneGeometry(400, 400, 32, 32),
- new THREE.MeshStandardMaterial({
- color: 0x837860,
- transparent: true,
- opacity: 0.5,
- }));
- plane.position.set(0, -5, 0);
- plane.castShadow = false;
- plane.receiveShadow = true;
- plane.rotation.x = -Math.PI / 2;
- this._graphics.Scene.add(plane);
- this._visibilityGrid = new visibility.VisibilityGrid(
- [new THREE.Vector3(-500, 0, -500), new THREE.Vector3(500, 0, 500)],
- [100, 100]);
- }
- _CreateBoids(geoLibrary) {
- const NUM_SMALL = _NUM_BOIDS * 2;
- const NUM_MEDIUM = _NUM_BOIDS / 2;
- const NUM_LARGE = _NUM_BOIDS / 20;
- const NUM_WHALES = 3;
- let params = {
- geometry: geoLibrary.fish,
- speedMin: 3.0,
- speedMax: 4.0,
- speed: _BOID_SPEED,
- maxSteeringForce: _BOID_FORCE_MAX,
- acceleration: _BOID_ACCELERATION,
- colour: 0x80FF80,
- };
- for (let i = 0; i < NUM_SMALL; i++) {
- const e = new Boid(this, params);
- this._entities.push(e);
- }
- params = {
- geometry: geoLibrary.fish,
- speedMin: 0.85,
- speedMax: 1.1,
- speed: _BOID_SPEED,
- maxSteeringForce: _BOID_FORCE_MAX,
- acceleration: _BOID_ACCELERATION,
- colour: 0x8080FF,
- };
- for (let i = 0; i < NUM_MEDIUM; i++) {
- const e = new Boid(this, params);
- this._entities.push(e);
- }
- params = {
- geometry: geoLibrary.fish,
- speedMin: 0.4,
- speedMax: 0.6,
- speed: _BOID_SPEED,
- maxSteeringForce: _BOID_FORCE_MAX / 4,
- acceleration: _BOID_ACCELERATION,
- colour: 0xFF8080,
- };
- for (let i = 0; i < NUM_LARGE; i++) {
- const e = new Boid(this, params);
- this._entities.push(e);
- }
- params = {
- geometry: geoLibrary.bigFish,
- speedMin: 0.1,
- speedMax: 0.12,
- speed: _BOID_SPEED,
- maxSteeringForce: _BOID_FORCE_MAX / 20,
- acceleration: _BOID_ACCELERATION,
- colour: 0xFF8080,
- };
- for (let i = 0; i < NUM_WHALES; i++) {
- const e = new Boid(this, params);
- e._group.position.y = math.rand_range(23, 26);
- this._entities.push(e);
- }
- //this._entities[0].DisplayDebug();
- }
- _OnStep(timeInSeconds) {
- timeInSeconds = Math.min(timeInSeconds, 1 / 10.0);
- if (this._entities.length == 0) {
- return;
- }
- // const eye = this._entities[0].Position.clone();
- // const dir = this._entities[0].Direction.clone();
- // dir.multiplyScalar(5);
- // eye.sub(dir);
- //
- // const m = new THREE.Matrix4();
- // m.lookAt(eye, this._entities[0].Position, new THREE.Vector3(0, 1, 0));
- //
- // const q = new THREE.Quaternion();
- // q.setFromEuler(new THREE.Euler(Math.PI / 2, 0, 0));
- //
- // const oldPosition = this._graphics._camera.position;
- // this._graphics._camera.position.lerp(eye, 0.05);
- // this._graphics._camera.quaternion.copy(this._entities[0]._group.quaternion);
- // //this._graphics._camera.quaternion.multiply(q);
- // this._controls.enabled = false;
- for (let e of this._entities) {
- e.Step(timeInSeconds);
- }
- }
- }
- function _Main() {
- _APP = new FishDemo();
- }
- _Main();
|