agent.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
  2. import {math} from './math.js';
  3. export const agent = (function() {
  4. const _BOID_FORCE_ORIGIN = 5;
  5. const _M = new THREE.Matrix4();
  6. const _V = new THREE.Vector3();
  7. const _A = new THREE.Vector2();
  8. const _B = new THREE.Vector2();
  9. const _AP = new THREE.Vector2();
  10. const _AB = new THREE.Vector2();
  11. const _BA = new THREE.Vector2();
  12. const _PT2 = new THREE.Vector2();
  13. const _PT3 = new THREE.Vector3();
  14. const _Q = new THREE.Quaternion();
  15. const _V_0 = new THREE.Vector3(0, 0, 0);
  16. const _V_Y = new THREE.Vector3(0, 1, 0);
  17. const _V_SC_0_1 = new THREE.Vector3(0.1, 0.1, 0.1);
  18. function _Key(x, y) {
  19. return x + '.' + y;
  20. }
  21. class LineRenderer {
  22. constructor(game) {
  23. this._game = game;
  24. this._materials = {};
  25. this._group = new THREE.Group();
  26. this._game._graphics.Scene.add(this._group);
  27. }
  28. Reset() {
  29. this._lines = [];
  30. this._group.remove(...this._group.children);
  31. }
  32. Add(pt1, pt2, hexColour) {
  33. const geometry = new THREE.Geometry();
  34. geometry.vertices.push(pt1.clone());
  35. geometry.vertices.push(pt2.clone());
  36. let material = this._materials[hexColour];
  37. if (!material) {
  38. this._materials[hexColour] = new THREE.LineBasicMaterial(
  39. {
  40. color: hexColour,
  41. linewidth: 3,
  42. });
  43. material = this._materials[hexColour];
  44. }
  45. const line = new THREE.Line(geometry, material);
  46. this._lines.push(line);
  47. this._group.add(line);
  48. }
  49. }
  50. class _Agent_Base {
  51. constructor(game, params) {
  52. this._direction = new THREE.Vector3(1, 0, 0);
  53. this._velocity = new THREE.Vector3(0, 0, 0);
  54. this._maxSteeringForce = params.maxSteeringForce;
  55. this._maxSpeed = params.speed;
  56. this._acceleration = params.acceleration;
  57. this._game = game;
  58. this._astar = params.astar;
  59. this._pathNodes = [];
  60. this._wanderAngle = 0;
  61. this._displayDebug = false;
  62. this._InitDebug();
  63. }
  64. _InitDebug() {
  65. if (!this._displayDebug) {
  66. return;
  67. }
  68. const e = this._astar._nodes[this._astar._end].metadata.position;
  69. const endPosition = new THREE.Vector3(e.x, 1, e.y);
  70. const geometry = new THREE.SphereGeometry(1, 16, 16);
  71. const material = new THREE.MeshBasicMaterial({
  72. color: 0x80FF80,
  73. transparent: true,
  74. opacity: 0.25,
  75. });
  76. const mesh = new THREE.Mesh(geometry, material);
  77. this._goalMesh = mesh;
  78. this._goalMesh.position.copy(endPosition);
  79. this._displayDebug = true;
  80. this._lineRenderer = new LineRenderer(this._game);
  81. this._lineRenderer.Reset();
  82. }
  83. _UpdateDebug() {
  84. if (!this._displayDebug) {
  85. return;
  86. }
  87. this._lineRenderer.Reset();
  88. this._UpdateDebugPath();
  89. }
  90. _UpdateDebugPath() {
  91. if (!this._displayDebug) {
  92. return;
  93. }
  94. const path = this._pathNodes;
  95. for (let i = 0; i < path.length - 1; i++) {
  96. const _AsV3 = (p) => {
  97. const pos = p.metadata.position;
  98. return new THREE.Vector3(pos.x + 0.5, 0.25, pos.y + 0.5);
  99. }
  100. const p1 = _AsV3(path[i]);
  101. const p2 = _AsV3(path[i+1]);
  102. this._lineRenderer.Add(p1, p2, 0xFF0000);
  103. }
  104. }
  105. get Position() {
  106. return this._group.position;
  107. }
  108. get Velocity() {
  109. return this._velocity;
  110. }
  111. get Direction() {
  112. return this._direction;
  113. }
  114. get Radius() {
  115. return this._radius;
  116. }
  117. Step(timeInSeconds) {
  118. if (this._astar) {
  119. if (this._astar.finished) {
  120. this._pathNodes = this._astar.Path;
  121. this._astar = null;
  122. } else if (!this._astar.started) {
  123. this._UpdateSearchStartPosition();
  124. }
  125. }
  126. this._ApplySteering(timeInSeconds);
  127. this._OnStep(timeInSeconds);
  128. }
  129. _UpdateSearchStartPosition() {
  130. const p = this.Position;
  131. const a = _A.set(p.x, p.z);
  132. const k = _Key(Math.floor(a.x), Math.floor(a.y));
  133. this._astar._start = k;
  134. }
  135. _ApplySteering(timeInSeconds) {
  136. const forces = [
  137. this._ApplyPathFollowing(),
  138. ];
  139. const steeringForce = new THREE.Vector3(0, 0, 0);
  140. for (const f of forces) {
  141. steeringForce.add(f);
  142. }
  143. steeringForce.multiplyScalar(this._acceleration * timeInSeconds);
  144. // Lock movement to x/z dimension
  145. steeringForce.multiply(new THREE.Vector3(1, 0, 1));
  146. // Clamp the force applied
  147. if (steeringForce.length() > this._maxSteeringForce) {
  148. steeringForce.normalize();
  149. steeringForce.multiplyScalar(this._maxSteeringForce);
  150. }
  151. this._velocity.add(steeringForce);
  152. // Clamp velocity
  153. if (this._velocity.length() > this._maxSpeed) {
  154. this._velocity.normalize();
  155. this._velocity.multiplyScalar(this._maxSpeed);
  156. }
  157. this._direction.copy(this._velocity);
  158. this._direction.normalize();
  159. }
  160. _ApplyPartialPathFollowing() {
  161. if (!this._astar) {
  162. return _V_0;
  163. }
  164. const end = _A.copy(this._astar._nodes[this._astar._end].metadata.position);
  165. end.addScalar(0.5);
  166. _PT3.set(end.x, this.Position.y, end.y);
  167. return this._ApplySeek(_PT3);
  168. }
  169. _ApplyPathFollowing() {
  170. if (this._pathNodes.length < 2) {
  171. return this._ApplyPartialPathFollowing();
  172. }
  173. const _PointOnLine = (p, a, b) => {
  174. _AP.subVectors(p, a);
  175. _AB.subVectors(b, a);
  176. const maxLength = _AB.length();
  177. const dp = math.clamp(_AP.dot(_AB), 0, maxLength);
  178. _AB.normalize();
  179. _AB.multiplyScalar(dp);
  180. return _AB.add(a);
  181. }
  182. const p = this.Position;
  183. _PT2.set(p.x, p.z);
  184. const a = _A.copy(this._pathNodes[0].metadata.position);
  185. const b = _B.copy(this._pathNodes[1].metadata.position);
  186. a.addScalar(0.5);
  187. b.addScalar(0.5);
  188. const pt = _PointOnLine(_PT2, a, b);
  189. _BA.subVectors(b, a).normalize();
  190. pt.add(_BA.multiplyScalar(0.1));
  191. _PT3.set(pt.x, p.y, pt.y);
  192. if (this._displayDebug) {
  193. this._lineRenderer.Add(p, _PT3, 0x00FF00);
  194. }
  195. if (p.distanceTo(_PT3) < 0.25) {
  196. this._pathNodes.shift();
  197. }
  198. return this._ApplySeek(_PT3);
  199. }
  200. _ApplySeek(destination) {
  201. const direction = destination.clone().sub(this.Position);
  202. direction.normalize();
  203. const forceVector = direction.multiplyScalar(_BOID_FORCE_ORIGIN);
  204. return forceVector;
  205. }
  206. }
  207. class _Agent_Mesh extends _Agent_Base {
  208. constructor(game, params) {
  209. super(game, params);
  210. this._mesh = new THREE.Mesh(params.geometry, params.material);
  211. this._mesh.castShadow = true;
  212. this._mesh.receiveShadow = true;
  213. this._mesh.scale.setScalar(0.1);
  214. this._mesh.rotateX(-Math.PI / 2);
  215. this._group = new THREE.Group();
  216. this._group.add(this._mesh);
  217. this._group.position.copy(params.position);
  218. game._graphics.Scene.add(this._group);
  219. }
  220. _OnStep(timeInSeconds) {
  221. const frameVelocity = this._velocity.clone();
  222. frameVelocity.multiplyScalar(timeInSeconds);
  223. this._group.position.add(frameVelocity);
  224. const direction = this.Direction;
  225. const m = new THREE.Matrix4();
  226. m.lookAt(
  227. new THREE.Vector3(0, 0, 0),
  228. direction,
  229. new THREE.Vector3(0, 1, 0));
  230. this._group.quaternion.setFromRotationMatrix(m);
  231. }
  232. }
  233. class _Agent_Instanced extends _Agent_Base {
  234. constructor(game, params) {
  235. super(game, params);
  236. this._mesh = params.mesh;
  237. this._proxy = new THREE.Object3D();
  238. this._proxy.scale.setScalar(0.1);
  239. this._proxy.rotateX(-Math.PI / 2);
  240. this._group = new THREE.Object3D();
  241. this._group.position.copy(params.position);
  242. this._group.add(this._proxy);
  243. this._index = params.index;
  244. }
  245. _OnStep(timeInSeconds) {
  246. const frameVelocity = this._velocity.clone();
  247. frameVelocity.multiplyScalar(timeInSeconds);
  248. this._group.position.add(frameVelocity);
  249. _M.lookAt(_V_0, this._direction, _V_Y);
  250. this._group.quaternion.setFromRotationMatrix(_M);
  251. this._group.updateMatrixWorld();
  252. this._proxy.updateMatrixWorld();
  253. this._mesh.setMatrixAt(this._index, this._proxy.matrixWorld);
  254. this._mesh.instanceMatrix.needsUpdate = true;
  255. }
  256. }
  257. class _Agent_Instanced_Faster extends _Agent_Base {
  258. constructor(game, params) {
  259. super(game, params);
  260. this._mesh = params.mesh;
  261. this._position = new THREE.Vector3();
  262. this._position.copy(params.position);
  263. this._index = params.index;
  264. }
  265. get Position() {
  266. return this._position;
  267. }
  268. _OnStep(timeInSeconds) {
  269. const frameVelocity = _V.copy(this._velocity);
  270. frameVelocity.multiplyScalar(timeInSeconds);
  271. this._position.add(frameVelocity);
  272. _Q.setFromUnitVectors(_V_Y, this._direction);
  273. _M.identity();
  274. _M.compose(this._position, _Q, _V_SC_0_1);
  275. this._mesh.setMatrixAt(this._index, _M);
  276. this._mesh.instanceMatrix.needsUpdate = true;
  277. }
  278. }
  279. return {
  280. Agent: _Agent_Mesh,
  281. Agent_Instanced: _Agent_Instanced_Faster
  282. };
  283. })();