brilliantsole 0.0.26 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +16 -10
  2. package/assets/3d/anchor.glb +0 -0
  3. package/assets/3d/coin.glb +0 -0
  4. package/assets/3d/glasses.glb +0 -0
  5. package/assets/3d/rightHand.glb +0 -0
  6. package/assets/audio/bounceMedium.wav +0 -0
  7. package/assets/audio/bounceStrong.wav +0 -0
  8. package/assets/audio/bounceWeak.wav +0 -0
  9. package/assets/audio/coin.wav +0 -0
  10. package/assets/audio/getUp.wav +0 -0
  11. package/assets/audio/grab.wav +0 -0
  12. package/assets/audio/kick.wav +0 -0
  13. package/assets/audio/platterFadeIn old.wav +0 -0
  14. package/assets/audio/platterFadeIn.wav +0 -0
  15. package/assets/audio/platterFadeOut.wav +0 -0
  16. package/assets/audio/punch.wav +0 -0
  17. package/assets/audio/punchSqueak.wav +0 -0
  18. package/assets/audio/purr.wav +0 -0
  19. package/assets/audio/purrFadeOut.wav +0 -0
  20. package/assets/audio/release.wav +0 -0
  21. package/assets/audio/splat.wav +0 -0
  22. package/assets/audio/stomp.wav +0 -0
  23. package/assets/images/ukaton-pressure-0.svg +9 -0
  24. package/assets/images/ukaton-pressure-1.svg +9 -0
  25. package/assets/images/ukaton-pressure-10.svg +9 -0
  26. package/assets/images/ukaton-pressure-11.svg +9 -0
  27. package/assets/images/ukaton-pressure-12.svg +9 -0
  28. package/assets/images/ukaton-pressure-13.svg +9 -0
  29. package/assets/images/ukaton-pressure-14.svg +9 -0
  30. package/assets/images/ukaton-pressure-15.svg +9 -0
  31. package/assets/images/ukaton-pressure-2.svg +9 -0
  32. package/assets/images/ukaton-pressure-3.svg +9 -0
  33. package/assets/images/ukaton-pressure-4.svg +9 -0
  34. package/assets/images/ukaton-pressure-5.svg +9 -0
  35. package/assets/images/ukaton-pressure-6.svg +9 -0
  36. package/assets/images/ukaton-pressure-7.svg +9 -0
  37. package/assets/images/ukaton-pressure-8.svg +9 -0
  38. package/assets/images/ukaton-pressure-9.svg +9 -0
  39. package/assets/images/ukaton-right-insole.svg +798 -0
  40. package/build/brilliantsole.cjs +2870 -882
  41. package/build/brilliantsole.cjs.map +1 -1
  42. package/build/brilliantsole.js +2477 -782
  43. package/build/brilliantsole.js.map +1 -1
  44. package/build/brilliantsole.ls.js +2260 -592
  45. package/build/brilliantsole.ls.js.map +1 -1
  46. package/build/brilliantsole.min.js +1 -1
  47. package/build/brilliantsole.min.js.map +1 -1
  48. package/build/brilliantsole.module.d.ts +302 -116
  49. package/build/brilliantsole.module.js +2468 -782
  50. package/build/brilliantsole.module.js.map +1 -1
  51. package/build/brilliantsole.module.min.d.ts +302 -116
  52. package/build/brilliantsole.module.min.js +1 -1
  53. package/build/brilliantsole.module.min.js.map +1 -1
  54. package/build/brilliantsole.node.module.d.ts +295 -113
  55. package/build/brilliantsole.node.module.js +2860 -882
  56. package/build/brilliantsole.node.module.js.map +1 -1
  57. package/build/dts/BS-output.d.ts +10 -0
  58. package/build/dts/BS.d.ts +21 -9
  59. package/build/dts/CameraManager.d.ts +72 -0
  60. package/build/dts/Device.d.ts +53 -16
  61. package/build/dts/DeviceInformationManager.d.ts +4 -4
  62. package/build/dts/DeviceManager.d.ts +3 -0
  63. package/build/dts/FileTransferManager.d.ts +18 -8
  64. package/build/dts/InformationManager.d.ts +8 -5
  65. package/build/dts/TfliteManager.d.ts +22 -2
  66. package/build/dts/WifiManager.d.ts +61 -0
  67. package/build/dts/connection/BaseConnectionManager.d.ts +37 -3
  68. package/build/dts/connection/ClientConnectionManager.d.ts +11 -2
  69. package/build/dts/connection/bluetooth/BluetoothConnectionManager.d.ts +1 -0
  70. package/build/dts/connection/bluetooth/NobleConnectionManager.d.ts +3 -1
  71. package/build/dts/connection/bluetooth/WebBluetoothConnectionManager.d.ts +2 -0
  72. package/build/dts/connection/bluetooth/bluetoothUUIDs.d.ts +2 -2
  73. package/build/dts/connection/udp/UDPConnectionManager.d.ts +28 -0
  74. package/build/dts/connection/webSocket/WebSocketConnectionManager.d.ts +25 -0
  75. package/build/dts/devicePair/DevicePair.d.ts +14 -10
  76. package/build/dts/devicePair/DevicePairPressureSensorDataManager.d.ts +8 -4
  77. package/build/dts/devicePair/DevicePairSensorDataManager.d.ts +2 -2
  78. package/build/dts/scanner/BaseScanner.d.ts +4 -1
  79. package/build/dts/scanner/NobleScanner.d.ts +2 -1
  80. package/build/dts/sensor/MotionSensorDataManager.d.ts +5 -2
  81. package/build/dts/sensor/SensorDataManager.d.ts +5 -4
  82. package/build/dts/server/BaseClient.d.ts +6 -3
  83. package/build/dts/server/ServerUtils.d.ts +1 -1
  84. package/build/dts/server/websocket/WebSocketUtils.d.ts +1 -1
  85. package/build/dts/utils/CenterOfPressureHelper.d.ts +2 -2
  86. package/build/dts/utils/Console.d.ts +2 -0
  87. package/build/dts/utils/MathUtils.d.ts +2 -0
  88. package/build/dts/utils/ThrottleUtils.d.ts +2 -0
  89. package/build/dts/vibration/VibrationManager.d.ts +19 -2
  90. package/build/index.d.ts +299 -113
  91. package/build/index.node.d.ts +292 -110
  92. package/examples/3d/scene.html +19 -5
  93. package/examples/3d/script.js +90 -17
  94. package/examples/3d-generic/index.html +144 -0
  95. package/examples/3d-generic/script.js +266 -0
  96. package/examples/balance/script.js +2 -1
  97. package/examples/basic/index.html +232 -18
  98. package/examples/basic/script.js +746 -106
  99. package/examples/bottango/index.html +11 -1
  100. package/examples/bottango/script.js +2 -2
  101. package/examples/center-of-pressure/index.html +114 -114
  102. package/examples/center-of-pressure/script.js +1 -1
  103. package/examples/device-pair/index.html +58 -58
  104. package/examples/device-pair/script.js +12 -8
  105. package/examples/edge-impulse/script.js +135 -44
  106. package/examples/edge-impulse-test/README.md +11 -0
  107. package/examples/edge-impulse-test/edge-impulse-standalone.js +7228 -0
  108. package/examples/edge-impulse-test/edge-impulse-standalone.wasm +0 -0
  109. package/examples/edge-impulse-test/index.html +75 -0
  110. package/examples/edge-impulse-test/run-impulse.js +135 -0
  111. package/examples/edge-impulse-test/script.js +200 -0
  112. package/examples/gloves/edge-impulse-standalone.js +7228 -0
  113. package/examples/gloves/edge-impulse-standalone.wasm +0 -0
  114. package/examples/gloves/index.html +119 -0
  115. package/examples/gloves/run-impulse.js +135 -0
  116. package/examples/gloves/scene.html +124 -0
  117. package/examples/gloves/script.js +931 -0
  118. package/examples/graph/index.html +11 -1
  119. package/examples/graph/script.js +94 -37
  120. package/examples/pressure/index.html +180 -12
  121. package/examples/pressure/script.js +144 -7
  122. package/examples/punch/index.html +135 -0
  123. package/examples/punch/punch.tflite +0 -0
  124. package/examples/punch/script.js +169 -0
  125. package/examples/recording/index.html +191 -183
  126. package/examples/server/index.html +109 -23
  127. package/examples/server/script.js +322 -111
  128. package/examples/ukaton-firmware-update/index.html +20 -0
  129. package/examples/ukaton-firmware-update/manifest.json +11 -0
  130. package/examples/ukaton-firmware-update/merged-firmware.bin +0 -0
  131. package/examples/utils/aframe/aframe-master.min.js +2 -0
  132. package/examples/utils/aframe/bs-vibration.js +150 -0
  133. package/examples/utils/aframe/force-pushable.js +80 -0
  134. package/examples/utils/aframe/grabbable-anchor.js +46 -0
  135. package/examples/utils/aframe/grabbable-listener.js +31 -0
  136. package/examples/utils/aframe/grabbable-physics-body.js +190 -0
  137. package/examples/utils/aframe/grow-shrink.js +25 -0
  138. package/examples/utils/aframe/hand-punch.js +119 -0
  139. package/examples/utils/aframe/my-obb-collider.js +293 -0
  140. package/examples/utils/aframe/occlude-hand-tracking-controls.js +47 -0
  141. package/examples/utils/aframe/occlude-mesh.js +42 -0
  142. package/examples/utils/aframe/palm-up-detector.js +47 -0
  143. package/examples/utils/aframe/shadow-material.js +20 -0
  144. package/examples/utils/aframe/soft-shadow-light.js +9 -0
  145. package/examples/webxr/script.js +3 -3
  146. package/examples/webxr-2/assets/3d/soccerBall.glb +0 -0
  147. package/examples/webxr-2/assets/audio/shellBounce.wav +0 -0
  148. package/examples/webxr-2/assets/audio/shellHit.wav +0 -0
  149. package/examples/webxr-2/assets/audio/shellKick.wav +0 -0
  150. package/examples/webxr-2/assets/audio/soccerBounce.wav +0 -0
  151. package/examples/webxr-2/assets/audio/soccerKick.mp3 +0 -0
  152. package/examples/webxr-2/assets/images/shellTexture.png +0 -0
  153. package/examples/webxr-2/components/bs-ankle.js +337 -0
  154. package/examples/webxr-2/components/coin.js +84 -0
  155. package/examples/webxr-2/components/custom-wrap.js +17 -0
  156. package/examples/webxr-2/components/goomba.js +3250 -0
  157. package/examples/webxr-2/components/init-shell-material.js +215 -0
  158. package/examples/webxr-2/components/platter.js +172 -0
  159. package/examples/webxr-2/components/shell.js +374 -0
  160. package/examples/webxr-2/components/soccer-ball.js +250 -0
  161. package/examples/webxr-2/components/squashed-goomba.js +249 -0
  162. package/examples/webxr-2/edge-impulse-standalone.js +7228 -0
  163. package/examples/webxr-2/edge-impulse-standalone.wasm +0 -0
  164. package/examples/webxr-2/index.html +996 -0
  165. package/examples/webxr-2/kick.tflite +0 -0
  166. package/examples/webxr-2/kick2.tflite +0 -0
  167. package/examples/webxr-2/run-impulse.js +135 -0
  168. package/examples/webxr-2/script.js +384 -0
  169. package/package.json +2 -1
  170. package/src/.prettierrc +4 -0
  171. package/src/BS.ts +66 -9
  172. package/src/CameraManager.ts +499 -0
  173. package/src/Device.ts +620 -92
  174. package/src/DeviceInformationManager.ts +22 -11
  175. package/src/DeviceManager.ts +94 -25
  176. package/src/FileTransferManager.ts +146 -21
  177. package/src/FirmwareManager.ts +1 -1
  178. package/src/InformationManager.ts +62 -20
  179. package/src/TfliteManager.ts +172 -26
  180. package/src/WifiManager.ts +323 -0
  181. package/src/connection/BaseConnectionManager.ts +145 -30
  182. package/src/connection/ClientConnectionManager.ts +47 -11
  183. package/src/connection/bluetooth/BluetoothConnectionManager.ts +14 -3
  184. package/src/connection/bluetooth/NobleConnectionManager.ts +155 -42
  185. package/src/connection/bluetooth/WebBluetoothConnectionManager.ts +104 -35
  186. package/src/connection/bluetooth/bluetoothUUIDs.ts +40 -13
  187. package/src/connection/udp/UDPConnectionManager.ts +356 -0
  188. package/src/connection/websocket/WebSocketConnectionManager.ts +282 -0
  189. package/src/devicePair/DevicePair.ts +145 -49
  190. package/src/devicePair/DevicePairPressureSensorDataManager.ts +72 -24
  191. package/src/devicePair/DevicePairSensorDataManager.ts +5 -5
  192. package/src/scanner/BaseScanner.ts +49 -11
  193. package/src/scanner/NobleScanner.ts +81 -17
  194. package/src/sensor/BarometerSensorDataManager.ts +1 -1
  195. package/src/sensor/MotionSensorDataManager.ts +22 -7
  196. package/src/sensor/PressureSensorDataManager.ts +47 -13
  197. package/src/sensor/SensorConfigurationManager.ts +75 -24
  198. package/src/sensor/SensorDataManager.ts +107 -26
  199. package/src/server/BaseClient.ts +192 -37
  200. package/src/server/BaseServer.ts +201 -43
  201. package/src/server/ServerUtils.ts +39 -9
  202. package/src/server/udp/UDPServer.ts +74 -23
  203. package/src/server/udp/UDPUtils.ts +9 -2
  204. package/src/server/websocket/WebSocketClient.ts +30 -9
  205. package/src/server/websocket/WebSocketServer.ts +1 -1
  206. package/src/server/websocket/WebSocketUtils.ts +4 -2
  207. package/src/utils/CenterOfPressureHelper.ts +5 -5
  208. package/src/utils/Console.ts +62 -9
  209. package/src/utils/MathUtils.ts +31 -1
  210. package/src/utils/ParseUtils.ts +25 -6
  211. package/src/utils/ThrottleUtils.ts +62 -0
  212. package/src/utils/Timer.ts +1 -1
  213. package/src/utils/checksum.ts +1 -1
  214. package/src/utils/mcumgr.js +1 -1
  215. package/src/vibration/VibrationManager.ts +166 -40
@@ -0,0 +1,3250 @@
1
+ AFRAME.registerComponent("goomba", {
2
+ schema: {
3
+ template: { default: "#goombaTemplate", type: "selector" },
4
+ lookAt: { default: ".lookAt" },
5
+ lookAtRaycast: { default: "[data-world-mesh]" },
6
+ grabbable: { default: false },
7
+ physics: { default: false },
8
+ },
9
+
10
+ collisionFilterGroup: 1 << 1,
11
+ collisionFilterMask: (1 << 0) | (1 << 1),
12
+
13
+ sides: ["left", "right"],
14
+
15
+ showHitSphere: false,
16
+
17
+ eyeScalesRange: 0.08,
18
+ defaultEyeHeight: 1.69,
19
+
20
+ staticBody: "shape: none;",
21
+ dynamicBody: "shape: none;",
22
+ bodyString: "type: dynamic; shape: none;",
23
+ shapeMain: `shape: box;
24
+ halfExtents: 0.1 0.091 0.09;
25
+ offset: 0 0 0;`,
26
+
27
+ init: function () {
28
+ this.emotion = "default";
29
+
30
+ this.lookAtPosition = new THREE.Vector3();
31
+ this.lookAtRefocusVector = new THREE.Vector3();
32
+ this.lookAtRefocusAxis = new THREE.Vector3();
33
+ this.lookAtRefocusEuler = new THREE.Euler();
34
+
35
+ this.forwardVector = new THREE.Vector3(0, 0, 1);
36
+ this.upVector = new THREE.Vector3(0, 1, 0);
37
+ this.rightVector = new THREE.Vector3(1, 0, 0);
38
+ this.toVector = new THREE.Vector3();
39
+
40
+ this.worldQuaternion = new THREE.Quaternion();
41
+ this.playGrabSound = AFRAME.utils.throttle(
42
+ this.playGrabSound.bind(this),
43
+ 500
44
+ );
45
+ this.playReleaseSound = AFRAME.utils.throttle(
46
+ this.playReleaseSound.bind(this),
47
+ 500
48
+ );
49
+
50
+ this.playBounceSoundName = AFRAME.utils.throttle(
51
+ this.playBounceSoundName.bind(this),
52
+ 200
53
+ );
54
+
55
+ this.lastTimePunched = 0;
56
+ this.sphere = new THREE.Sphere();
57
+ this.sphere.radius = 0.2;
58
+
59
+ this.hands = {};
60
+
61
+ this.collisionVector = new THREE.Vector3();
62
+
63
+ this.lastPetTick = 0;
64
+ this.petPosition = new THREE.Vector3();
65
+ this.localPetPosition = new THREE.Vector3();
66
+
67
+ this.lastTimeCollidedWhenWalking;
68
+
69
+ this.el.shapeMain = this.shapeMain;
70
+
71
+ this.pointToWalkFrom = new THREE.Vector3();
72
+ this.pointToWalkTo = new THREE.Vector3();
73
+ this.tempPointToWalkTo = new THREE.Vector3();
74
+ this.clampedTempPointToWalkTo = new THREE.Vector3();
75
+ this.quaternionToTurnFrom = new THREE.Quaternion();
76
+ this.quaternionToTurnTo = new THREE.Quaternion();
77
+ this.tempEuler = new THREE.Euler();
78
+
79
+ this.lastTimeTurnedAround = 0;
80
+
81
+ this.pointToWalkToOffset = new THREE.Vector3();
82
+ this.pointToWalkToSphere = document.createElement("a-sphere");
83
+ this.pointToWalkToSphere.setAttribute("color", "green");
84
+ this.pointToWalkToSphere.setAttribute("visible", "false");
85
+ this.pointToWalkToSphere.setAttribute("radius", "0.01");
86
+ this.el.sceneEl.appendChild(this.pointToWalkToSphere);
87
+
88
+ this.wallIntersectionSphere = document.createElement("a-sphere");
89
+ this.wallIntersectionSphere.setAttribute("color", "green");
90
+ this.wallIntersectionSphere.setAttribute("visible", "false");
91
+ this.wallIntersectionSphere.setAttribute("radius", "0.01");
92
+ this.el.sceneEl.appendChild(this.wallIntersectionSphere);
93
+
94
+ this.lastWalkTick = 0;
95
+
96
+ this.rollQuaternionFrom = new THREE.Quaternion();
97
+ this.rollQuaternionTo = new THREE.Quaternion();
98
+ this.rollTempEuler = new THREE.Euler();
99
+
100
+ this.squashLookAtQuaternion1 = new THREE.Quaternion();
101
+ this.squashLookAtQuaternion2 = new THREE.Quaternion();
102
+
103
+ this.orientation = "upright";
104
+ this.scale = 1;
105
+ this.lastBlinkTick = 0;
106
+ this.blinkInterval = 5000;
107
+
108
+ const eyeScalesOffset =
109
+ Math.random() * this.eyeScalesRange - this.eyeScalesRange / 2;
110
+ this.eyesScales = {
111
+ left: { height: 1 + eyeScalesOffset, width: 1 },
112
+ right: { height: 1 - eyeScalesOffset, width: 1 },
113
+ };
114
+
115
+ this.hitSphere = document.createElement("a-sphere");
116
+ this.hitSphere.setAttribute("color", "blue");
117
+ this.hitSphere.setAttribute("visible", "false");
118
+ this.hitSphere.setAttribute("radius", "0.05");
119
+ this.el.sceneEl.appendChild(this.hitSphere);
120
+
121
+ this.updateLookAtTargets();
122
+ this.lookAtSelectorInterval = setInterval(
123
+ () => this.updateLookAtTargets(),
124
+ 1000
125
+ );
126
+ this.updateLookAtRaycastTargets();
127
+ this.lookAtRaycastSelectorInterval = setInterval(
128
+ () => this.updateLookAtRaycastTargets(),
129
+ 1000
130
+ );
131
+
132
+ this.pointToLookAt = new THREE.Vector3();
133
+ this.pointToLookAtDirection = new THREE.Vector3();
134
+ this.raycaster = new THREE.Raycaster();
135
+ this.ray = new THREE.Vector3();
136
+ this.rayEuler = new THREE.Euler();
137
+
138
+ this.eyeControllers = {};
139
+ this.eyeScales = {};
140
+ this.eyePupils = {};
141
+ this.eyeWhites = {};
142
+ this.eyeRotators = {};
143
+
144
+ this.el.classList.add("goomba");
145
+ this.el.classList.add("lookAt");
146
+
147
+ this.eyeTickInterval = 100;
148
+ this.eyeRefocusInterval = 100;
149
+ this.wanderEyesInterval = 2000;
150
+
151
+ this.lookAtEuler = new THREE.Euler();
152
+ this.tempLookAtEuler = new THREE.Euler();
153
+
154
+ this.objectToLookAtDistance = 0;
155
+
156
+ this.worldPosition = new THREE.Vector3();
157
+ this.otherWorldPosition = new THREE.Vector3();
158
+
159
+ this.legs = {};
160
+ this.feet = {};
161
+
162
+ this.lastEyeTick = 0;
163
+ this.lastEyeRefocusTick = 0;
164
+ this.lastWanderEyesTick = 0;
165
+
166
+ this.lastChangeLookAtTick = 0;
167
+ this.changeLookAtInterval = 100;
168
+
169
+ this.status = "idle";
170
+
171
+ window.goombas = window.goombas || [];
172
+ window.goombas.push(this);
173
+
174
+ this.el.addEventListener("collide", this.onCollide.bind(this));
175
+ this.el.addEventListener(
176
+ "obbcollisionstarted",
177
+ this.onObbCollisionStarted.bind(this)
178
+ );
179
+
180
+ this.lastTimeLookedAt = 0;
181
+
182
+ this.camera = document.querySelector("a-camera");
183
+
184
+ this.el.addEventListener("loaded", () => {
185
+ this.el.addEventListener("punch", this.onPunch.bind(this));
186
+ this.el.addEventListener("shell", this.onShell.bind(this));
187
+
188
+ this.el.addEventListener("kick", this.onKick.bind(this));
189
+ this.el.addEventListener("stomp", this.onStomp.bind(this));
190
+
191
+ this.el.addEventListener("body-loaded", this.onBodyLoaded.bind(this));
192
+
193
+ const template = this.data.template.content
194
+ .querySelector("a-entity")
195
+ .cloneNode(true);
196
+ template.classList.forEach((_class) => {
197
+ // console.log(`adding class ${_class}`);
198
+ this.el.classList.add(_class);
199
+ });
200
+ if (this.el.classList.contains("punchable")) {
201
+ this.punchable = true;
202
+ }
203
+ Array.from(template.children).forEach((entity) => {
204
+ this.el.appendChild(entity);
205
+ });
206
+ this.template = template;
207
+
208
+ this.el.collider = this.el.querySelector(".collider").object3D;
209
+ this.el.setAttribute("obb-collider", "trackedObject3D: collider");
210
+
211
+ this.petEntity = this.el.querySelector(".pet");
212
+
213
+ this.el.addEventListener("raycaster-intersected", (event) => {
214
+ //console.log("looked at", event);
215
+ this.isLookedAt = true;
216
+ });
217
+ this.el.addEventListener("raycaster-intersected-cleared", (event) => {
218
+ //console.log("stopped looked at", event);
219
+ this.isLookedAt = false;
220
+ this.lastTimeLookedAt = this.latestTick;
221
+ });
222
+
223
+ this.squash = this.el.querySelector(".squash");
224
+ this.body = this.el.querySelector(".body");
225
+
226
+ this.el.querySelectorAll(".left").forEach((entity) => {
227
+ const duplicate = entity.cloneNode(true);
228
+ duplicate.addEventListener("loaded", () => {
229
+ this.flip(duplicate);
230
+ duplicate.classList.remove("left");
231
+ duplicate.classList.add("right");
232
+ duplicate
233
+ .querySelectorAll("[position], [rotation]")
234
+ .forEach((entity) => this.flip(entity));
235
+
236
+ if (entity.classList.contains("eye")) {
237
+ this.sides.forEach((side) => {
238
+ this.eyeControllers[side] = this.el.querySelector(
239
+ `.${side}.eye .controller`
240
+ );
241
+ this.eyeRotators[side] = this.el.querySelector(
242
+ `.${side}.eye .white`
243
+ );
244
+ this.eyeScales[side] = this.el.querySelector(
245
+ `.${side}.eye .white`
246
+ );
247
+ this.eyePupils[side] = this.el.querySelector(
248
+ `.${side}.eye .black`
249
+ );
250
+ this.eyeWhites[side] = this.el.querySelector(
251
+ `.${side}.eye .white`
252
+ );
253
+
254
+ this.setEyeScale(side, this.eyesScales[side]);
255
+ });
256
+ }
257
+ if (entity.classList.contains("leg")) {
258
+ this.sides.forEach((side) => {
259
+ this.legs[side] = this.el.querySelector(`.${side}.leg`);
260
+ this.feet[side] = this.el.querySelector(`.${side}.leg .foot`);
261
+ });
262
+ }
263
+ });
264
+ entity.parentEl.appendChild(duplicate);
265
+ });
266
+ setTimeout(() => {
267
+ if (this.data.grabbable) {
268
+ this.el.setAttribute("grabbable", "");
269
+ }
270
+ if (this.data.physics) {
271
+ this.el.setAttribute(
272
+ "grabbable-physics-body",
273
+ `type: dynamic; dynamic-body: "shape: none;";`
274
+ );
275
+ }
276
+
277
+ if (window.goombas.indexOf(this) == window.goombas.length - 1) {
278
+ const raycaster = this.camera.getAttribute("_raycaster");
279
+ this.camera.removeAttribute("raycaster");
280
+ this.camera.setAttribute("raycaster", raycaster);
281
+ }
282
+
283
+ Array.from(
284
+ document.querySelectorAll("[hand-tracking-controls]")
285
+ ).forEach((entity) => {
286
+ const component = entity.components["hand-tracking-controls"];
287
+ const side = component.data.hand;
288
+ this.hands[side] = component;
289
+ });
290
+
291
+ this.el.emit("goomba-loaded");
292
+ }, 1);
293
+ });
294
+
295
+ this.setGrabEnabled = AFRAME.utils.throttleLeadingAndTrailing(
296
+ this.setGrabEnabled.bind(this),
297
+ 70
298
+ );
299
+
300
+ this.el.addEventListener("grabstarted", () => this.setGrabEnabled(true));
301
+ this.el.addEventListener("grabended", () => this.setGrabEnabled(false));
302
+ },
303
+
304
+ // validFloorMeshTypes: ["floor", "table"],
305
+ validFloorMeshTypes: ["floor", "table", "desk"],
306
+ validHitWorldMeshTypes: [],
307
+ //validHitWorldMeshTypes: ["floor", "table", "wall", "ceiling"],
308
+
309
+ onPunch: function (event) {
310
+ const { velocity, position } = event.detail;
311
+ this.punch(velocity, position);
312
+ },
313
+
314
+ onKick: function (event) {
315
+ const { velocity } = event.detail;
316
+ //console.log("onKick", { velocity });
317
+ this.punch(velocity);
318
+ },
319
+
320
+ onShell: function (event) {
321
+ const { velocity, position } = event.detail;
322
+ this.shellHit(velocity, position);
323
+ },
324
+ shellHit: function (velocity, position) {
325
+ this.shellHitOptions = { velocity, position };
326
+ },
327
+ shellVelocityPitchRange: {
328
+ min: THREE.MathUtils.degToRad(2),
329
+ max: THREE.MathUtils.degToRad(0),
330
+ },
331
+ shellVelocityInterpolationRange: {
332
+ min: 0.5,
333
+ max: 4,
334
+ },
335
+ shellVelocityLengthRange: {
336
+ min: 0.8,
337
+ max: 1.1,
338
+ },
339
+ _shellHit: function (velocity, position) {
340
+ if (this.ignorePunchStatuses.includes(this.status)) {
341
+ return;
342
+ }
343
+ this.playShellHitSound();
344
+ this.playPunchSqueakSound();
345
+
346
+ this.setStatus("idle");
347
+ this.lastTimePunched = this.latestTick;
348
+ if (true) {
349
+ this.shelled = true;
350
+ } else {
351
+ this.punched = true;
352
+ this.punchedFloor = this.floor;
353
+ }
354
+
355
+ let pitch = 0;
356
+ if (true) {
357
+ pitch = THREE.MathUtils.lerp(
358
+ this.shellVelocityPitchRange.min,
359
+ this.shellVelocityPitchRange.max,
360
+ Math.random()
361
+ );
362
+ }
363
+
364
+ let _yaw = 0;
365
+ if (false) {
366
+ _yaw = Math.random() * Math.PI * 2;
367
+ } else {
368
+ _yaw = Math.atan2(velocity.x, velocity.z);
369
+ }
370
+
371
+ const velocityLength = velocity.length();
372
+ let velocityInterpolation = THREE.MathUtils.inverseLerp(
373
+ this.shellVelocityInterpolationRange.min,
374
+ this.shellVelocityInterpolationRange.max,
375
+ velocityLength
376
+ );
377
+ velocityInterpolation = THREE.MathUtils.clamp(velocityInterpolation, 0, 1);
378
+ console.log("velocityInterpolation", velocityInterpolation);
379
+
380
+ let newVelocityLength = THREE.MathUtils.lerp(
381
+ this.shellVelocityLengthRange.min,
382
+ this.shellVelocityLengthRange.max,
383
+ velocityInterpolation
384
+ );
385
+ console.log("newVelocityLength", newVelocityLength);
386
+
387
+ const newVelocity = new THREE.Vector3(0, newVelocityLength, 0);
388
+ const euler = new THREE.Euler(pitch, _yaw, 0, "YXZ");
389
+ newVelocity.applyEuler(euler);
390
+ this.physicsOptions = { velocity: newVelocity };
391
+ this.setPhysicsEnabled(true);
392
+ },
393
+
394
+ // adjust values as needed
395
+ stompDistanceRange: { min: 0.3, max: 5 },
396
+ stompDistanceInterpolationPower: 1,
397
+ stompDelayRange: { min: 0, max: 350 },
398
+ stompVelocityLengthRange: { min: 0.15, max: 1 },
399
+ stompVelocityPitchRange: {
400
+ min: THREE.MathUtils.degToRad(2),
401
+ max: THREE.MathUtils.degToRad(0),
402
+ },
403
+ ignoreStompStatuses: ["getting up", "falling", "petting"],
404
+ onStomp: function (event) {
405
+ const { distance, yaw, kill } = event.detail;
406
+ //console.log("onStomp", { distance, yaw, kill });
407
+ this.stomp(distance, yaw, kill);
408
+ },
409
+ stomp: function (distance, yaw, kill) {
410
+ this.stompOptions = { distance, yaw, kill };
411
+ },
412
+ _stomp: function (distance, yaw, kill) {
413
+ if (!this.floor || this.ignoreStompStatuses.includes(this.status)) {
414
+ return;
415
+ }
416
+ if (kill) {
417
+ this.deathCollidedEntity = this.floor;
418
+ this.shouldDie = true;
419
+ this.deathVelocity = new THREE.Vector3(0, -1, 0);
420
+ this.deathNormal = new THREE.Vector3(0, 1, 0);
421
+ } else {
422
+ if (false) {
423
+ this.playPunchSqueakSound();
424
+ }
425
+ let distanceInterpolation = THREE.MathUtils.inverseLerp(
426
+ this.stompDistanceRange.min,
427
+ this.stompDistanceRange.max,
428
+ distance
429
+ );
430
+ distanceInterpolation = THREE.MathUtils.clamp(
431
+ distanceInterpolation,
432
+ 0,
433
+ 1
434
+ );
435
+ distanceInterpolation = Math.pow(
436
+ distanceInterpolation,
437
+ this.stompDistanceInterpolationPower
438
+ );
439
+ // console.log({ distanceInterpolation });
440
+ const delay = THREE.MathUtils.lerp(
441
+ this.stompDelayRange.min,
442
+ this.stompDelayRange.max,
443
+ distanceInterpolation
444
+ );
445
+ // console.log({ delay });
446
+ const applyStomp = () => {
447
+ this.setStatus("idle");
448
+ // console.log("stomped", velocity);
449
+ const velocityLength = THREE.MathUtils.lerp(
450
+ this.stompVelocityLengthRange.min,
451
+ this.stompVelocityLengthRange.max,
452
+ 1 - distanceInterpolation
453
+ );
454
+ let pitch = 0;
455
+ if (true) {
456
+ pitch = THREE.MathUtils.lerp(
457
+ this.stompVelocityPitchRange.min,
458
+ this.stompVelocityPitchRange.max,
459
+ Math.random()
460
+ );
461
+ } else {
462
+ pitch = THREE.MathUtils.lerp(
463
+ this.stompVelocityPitchRange.min,
464
+ this.stompVelocityPitchRange.max,
465
+ distanceInterpolation
466
+ );
467
+ }
468
+
469
+ let _yaw = 0;
470
+ if (true) {
471
+ _yaw = Math.random() * Math.PI * 2;
472
+ } else {
473
+ _yaw = yaw;
474
+ }
475
+
476
+ const velocity = new THREE.Vector3(0, velocityLength, 0);
477
+ const euler = new THREE.Euler(pitch, _yaw, 0, "YXZ");
478
+ velocity.applyEuler(euler);
479
+ this.physicsOptions = { velocity };
480
+ this.setPhysicsEnabled(true);
481
+ };
482
+ if (true) {
483
+ applyStomp();
484
+ } else {
485
+ setTimeout(() => applyStomp(), delay);
486
+ }
487
+ }
488
+ },
489
+
490
+ setGrabEnabled: function (enabled) {
491
+ if (enabled) {
492
+ this.onGrabStarted();
493
+ } else {
494
+ this.onGrabEnded();
495
+ }
496
+ },
497
+
498
+ ignorePunchStatuses: ["falling", "getting up"],
499
+
500
+ punchDownPitchThreshold: THREE.MathUtils.degToRad(-40),
501
+ punch: function (velocity, position) {
502
+ this.punchOptions = { velocity, position };
503
+ },
504
+ _punch: function (velocity, position) {
505
+ if (this.ignorePunchStatuses.includes(this.status)) {
506
+ return;
507
+ }
508
+ this.playPunchSqueakSound();
509
+ this.playPunchSound();
510
+ const { x, y, z } = velocity;
511
+ const pitch = Math.atan2(y, Math.sqrt(x * x + z * z));
512
+ // console.log({ pitch, threshold: this.punchDownPitchThreshold });
513
+ if (pitch < this.punchDownPitchThreshold && this.floor) {
514
+ this.deathCollidedEntity = this.floor;
515
+ //console.log("punching down on floor", this.deathCollidedEntity);
516
+ this.shouldDie = true;
517
+ this.deathVelocity = velocity.clone();
518
+ this.deathNormal = new THREE.Vector3(0, 1, 0);
519
+ return;
520
+ }
521
+ this.setStatus("idle");
522
+ this.lastTimePunched = this.latestTick;
523
+ this.punched = true;
524
+ this.punchedFloor = this.floor;
525
+ if (!position) {
526
+ position = this.el.object3D.getWorldPosition(new THREE.Vector3());
527
+ }
528
+ // console.log("punched", velocity, position);
529
+ this.physicsOptions = { velocity, position };
530
+ this.setPhysicsEnabled(true);
531
+ },
532
+
533
+ velocityScalar: 4,
534
+
535
+ onBodyLoaded: function (event) {
536
+ const { body } = event.detail;
537
+ body.collisionFilterGroup = this.collisionFilterGroup;
538
+ body.collisionFilterMask = this.collisionFilterMask;
539
+ //body.linearDamping = 0;
540
+ //body.angularDamping = 0;
541
+ this.physicsBody = body;
542
+ body.material =
543
+ this.el.sceneEl.systems["physics"].driver.getMaterial("goomba");
544
+ // console.log(body.material);
545
+ // console.log("onBodyLoaded");
546
+ if (this.physicsOptions) {
547
+ const { position, velocity } = this.physicsOptions;
548
+ this.physicsOptions = undefined;
549
+
550
+ const body = this.el.body;
551
+ if (body) {
552
+ body.wakeUp();
553
+ setTimeout(() => {
554
+ body.wakeUp();
555
+ this.setStatus("falling");
556
+ this.landed = false;
557
+ this.floor = undefined;
558
+
559
+ if (false) {
560
+ console.log("applying impulse", velocity, position);
561
+ body.applyImpulse(
562
+ new CANNON.Vec3().copy(velocity.multiplyScalar(this.punchScalar)),
563
+ new CANNON.Vec3().copy(position)
564
+ );
565
+ } else {
566
+ // console.log("setting velocity", velocity);
567
+ body.velocity.set(
568
+ velocity.x * this.velocityScalar,
569
+ velocity.y * this.velocityScalar,
570
+ velocity.z * this.velocityScalar
571
+ );
572
+
573
+ const strength = velocity.length();
574
+ const angularVelocity = new THREE.Vector3(strength * 20, 0, 0);
575
+ const normalizedVelocity = velocity.clone().normalize();
576
+ const angle = Math.atan2(
577
+ normalizedVelocity.x,
578
+ normalizedVelocity.z
579
+ );
580
+ const euler = new THREE.Euler(0, angle, 0);
581
+ angularVelocity.applyEuler(euler);
582
+ // console.log("setting angularVelocity", angularVelocity);
583
+ body.angularVelocity.set(...angularVelocity.toArray());
584
+ }
585
+ }, 1);
586
+ } else {
587
+ console.error("body not found");
588
+ }
589
+ }
590
+ },
591
+
592
+ floorCollisionNormalThreshold: THREE.MathUtils.degToRad(10),
593
+ onCollide: async function (event) {
594
+ const collidedEntity = event.detail.body.el;
595
+ // console.log("collided with", collidedEntity, event.detail);
596
+ // console.log(this.physicsBody.material, event.detail.body.material);
597
+
598
+ // FIX - make sure intersection from raycaster actually has intersection (e.g. hitting edge of table)
599
+ if (
600
+ this.punched &&
601
+ collidedEntity.dataset.worldMesh &&
602
+ (this.validHitWorldMeshTypes.length == 0 ||
603
+ this.validHitWorldMeshTypes.includes(
604
+ collidedEntity.dataset.worldMesh
605
+ )) &&
606
+ collidedEntity != this.punchedFloor
607
+ ) {
608
+ let didDie = false;
609
+ if (true) {
610
+ this.ray.copy(this.el.body.velocity);
611
+ this.raycaster.set(this.el.object3D.position, this.ray);
612
+ this.raycaster.near = 0;
613
+ this.raycaster.far = 1;
614
+
615
+ const intersectable =
616
+ collidedEntity.components["occlude-mesh"]?.raycastMesh;
617
+ if (intersectable) {
618
+ const intersections = this.raycaster.intersectObjects(
619
+ [intersectable],
620
+ true
621
+ );
622
+ if (intersections[0]) {
623
+ console.log("death intersection", intersections[0]);
624
+ didDie = true;
625
+ } else {
626
+ console.log("no intersections found for death punch");
627
+ }
628
+ }
629
+ } else {
630
+ didDie = true;
631
+ }
632
+
633
+ if (didDie) {
634
+ console.log("died colliding with", collidedEntity);
635
+ this.deathCollidedEntity = collidedEntity;
636
+ this.shouldDie = true;
637
+ this.deathVelocity = this.el.body.velocity.clone();
638
+ this.deathNormal = event.detail.contact.ni.clone();
639
+ }
640
+ }
641
+
642
+ switch (this.status) {
643
+ case "falling":
644
+ if (
645
+ this.validFloorMeshTypes.length == 0 ||
646
+ this.validFloorMeshTypes.includes(collidedEntity.dataset.worldMesh)
647
+ ) {
648
+ const normal = event.detail.contact.ni;
649
+ const upAngle = this.worldBasis.up.angleTo(
650
+ new THREE.Vector3(normal.x, normal.y, normal.z)
651
+ );
652
+ if (upAngle <= this.floorCollisionNormalThreshold) {
653
+ this.resetLegs();
654
+ this.setFloor(collidedEntity);
655
+ }
656
+ }
657
+
658
+ if (!this.punched) {
659
+ const velocity = this.el.body.velocity;
660
+ this.playBounceSound(velocity);
661
+ }
662
+ break;
663
+ }
664
+ },
665
+
666
+ bounceThresholds: {
667
+ strong: 4,
668
+ medium: 2,
669
+ weak: 0.2,
670
+ },
671
+ playBounceSound: function (velocity) {
672
+ const length = velocity.length();
673
+
674
+ let soundName;
675
+ if (length > this.bounceThresholds.strong) {
676
+ soundName = "bounceStrongSound";
677
+ } else if (length > this.bounceThresholds.medium) {
678
+ soundName = "bounceMediumSound";
679
+ } else if (length > this.bounceThresholds.weak) {
680
+ soundName = "bounceWeakSound";
681
+ }
682
+
683
+ // console.log("bounce", length, soundName);
684
+
685
+ if (!soundName) {
686
+ return;
687
+ }
688
+
689
+ this.playBounceSoundName(soundName);
690
+ },
691
+ playBounceSoundName: async function (soundName) {
692
+ this.returnSound("bounceWeakSound");
693
+ this.returnSound("bounceMediumSound");
694
+ this.returnSound("bounceStrongSound");
695
+
696
+ this.playSound(soundName);
697
+ },
698
+
699
+ remove: function () {
700
+ if (window.goombas.includes(this)) {
701
+ // console.log("removing goomba");
702
+ window.goombas.splice(window.goombas.indexOf(this), 1);
703
+ }
704
+
705
+ clearInterval(this.lookAtSelectorInterval);
706
+ clearInterval(this.lookAtRaycastSelectorInterval);
707
+ clearInterval(this.floorInterval);
708
+
709
+ if (this.status == "petting") {
710
+ this.el.sceneEl.emit("stopPetting", { side: this.petSide });
711
+ }
712
+ if (this.purrSound) {
713
+ this.stopPurrSound();
714
+ }
715
+ },
716
+
717
+ pause: function () {
718
+ if (this.status == "petting") {
719
+ this.el.sceneEl.emit("stopPetting", { side: this.petSide });
720
+ }
721
+
722
+ if (this.purrSound) {
723
+ this.stopPurrSound();
724
+ this.playPurrFadeOutSound();
725
+ }
726
+ },
727
+
728
+ debugColors: false,
729
+
730
+ onObbCollisionStarted: async function (event) {
731
+ const collidedEntity = event.detail.withEl;
732
+ const otherGoomba = collidedEntity.components["goomba"];
733
+ switch (this.status) {
734
+ case "walking":
735
+ if (otherGoomba) {
736
+ if (
737
+ otherGoomba.status == "walking" &&
738
+ this.collidedWhenWalking &&
739
+ otherGoomba.collidedWhenWalking
740
+ ) {
741
+ return;
742
+ }
743
+ if (
744
+ this.lastTimeCollidedWhenWalking != undefined &&
745
+ this.latestTick - this.lastTimeCollidedWhenWalking < 200
746
+ ) {
747
+ return;
748
+ }
749
+ if (
750
+ otherGoomba.lastTimeCollidedWhenWalking != undefined &&
751
+ otherGoomba.latestTick - otherGoomba.lastTimeCollidedWhenWalking <
752
+ 200
753
+ ) {
754
+ return;
755
+ }
756
+ if (
757
+ this.lastCollisionGoomba == otherGoomba &&
758
+ otherGoomba.lastCollisionGoomba == this
759
+ ) {
760
+ return;
761
+ }
762
+
763
+ if (this.debugColors) {
764
+ this.body.setAttribute("color", "yellow");
765
+ otherGoomba.body.setAttribute("color", "yellow");
766
+ }
767
+
768
+ this.collidedWhenWalking = true;
769
+ otherGoomba.collidedWhenWalking = true;
770
+
771
+ if (true) {
772
+ if (true) {
773
+ this.collisionVector.subVectors(
774
+ otherGoomba.el.object3D.position,
775
+ this.el.object3D.position
776
+ );
777
+ let angle = Math.atan2(
778
+ this.collisionVector.x,
779
+ this.collisionVector.z
780
+ );
781
+ angle = THREE.MathUtils.radToDeg(angle);
782
+
783
+ let offset = 90;
784
+ if (
785
+ Math.abs(this.angle - (angle + offset)) >
786
+ Math.abs(this.angle - (angle - offset))
787
+ ) {
788
+ offset = -90;
789
+ }
790
+
791
+ this.collidedNewAngle = angle + offset;
792
+ otherGoomba.collidedNewAngle = angle + 180 + offset;
793
+ } else {
794
+ this.collidedNewAngle = this.angle + offset;
795
+ otherGoomba.collidedNewAngle = otherGoomba.angle + offset;
796
+ }
797
+ } else {
798
+ const sign = Math.round(Math.random()) ? 1 : -1;
799
+ const offset = 90 * sign;
800
+ this.collidedNewAngle = otherGoomba.angle;
801
+ otherGoomba.collidedNewAngle = this.angle;
802
+ }
803
+
804
+ this.lastCollisionGoomba = otherGoomba;
805
+ otherGoomba.lastCollisionGoomba = this;
806
+
807
+ this.lastWalkTick = -this.walkInterval;
808
+ otherGoomba.lastWalkTick = -otherGoomba.walkInterval;
809
+
810
+ // console.log(
811
+ // "collided with other goomba",
812
+ // this.collidedNewAngle,
813
+ // otherGoomba.collidedNewAngle
814
+ // );
815
+ }
816
+ break;
817
+ }
818
+ },
819
+
820
+ isFlatOnOneSide: function () {
821
+ this.checkOrientation();
822
+ return this.orientation != "unknown";
823
+ },
824
+
825
+ setFloor: function (newFloor) {
826
+ clearInterval(this.floor);
827
+ if (this.floor == newFloor) {
828
+ return;
829
+ }
830
+ this.floor = newFloor;
831
+ // console.log("setting floor", newFloor);
832
+ if (this.floor) {
833
+ clearInterval(this.floorInterval);
834
+ const checkIfStoppedMoving = () => {
835
+ const stoppedMoving =
836
+ this.el.components["dynamic-body"].body.velocity.length() < 0.01;
837
+ const stoppedRotating =
838
+ this.el.components["dynamic-body"].body.angularVelocity.length() <
839
+ 0.01;
840
+ const isFlat = this.isFlatOnOneSide();
841
+ const stopped = stoppedMoving && stoppedRotating && isFlat;
842
+ // console.log({ stoppedMoving, stoppedRotating, isFlat });
843
+ if (stopped) {
844
+ // console.log("stopped");
845
+ if (this.punched) {
846
+ this.punched = false;
847
+ }
848
+ this.landed = true;
849
+ clearInterval(this.floorInterval);
850
+ this.setPhysicsEnabled(false);
851
+ setTimeout(() => {
852
+ this.setStatus("getting up");
853
+ }, 1);
854
+ }
855
+ };
856
+ this.floorInterval = setInterval(() => {
857
+ checkIfStoppedMoving();
858
+ }, 50);
859
+ checkIfStoppedMoving();
860
+ } else {
861
+ this.setPhysicsEnabled(false);
862
+ }
863
+ },
864
+
865
+ onGrabStarted: function () {
866
+ // console.log("onGrabStarted");
867
+ switch (this.status) {
868
+ default:
869
+ this.setStatus("grabbed");
870
+ // if (this.data.physics) {
871
+ // this.setPhysicsEnabled(false);
872
+ // }
873
+ break;
874
+ }
875
+ },
876
+ onGrabEnded: function () {
877
+ // console.log("onGrabEnded");
878
+ switch (this.status) {
879
+ default:
880
+ if (this.data.physics) {
881
+ // this.setPhysicsEnabled(true);
882
+ this.setStatus("falling");
883
+ } else {
884
+ this.setStatus("idle");
885
+ }
886
+ break;
887
+ }
888
+ },
889
+
890
+ punchScalar: 5,
891
+
892
+ setPhysicsEnabled: function (enabled) {
893
+ this.updatePhysicsEnabledFlag = enabled;
894
+ },
895
+ updatePhysicsEnabled: function (enabled) {
896
+ if (enabled) {
897
+ this.el.removeAttribute("dynamic-body");
898
+ this.el.removeAttribute("static-body");
899
+ this.el.removeAttribute("shape__main");
900
+
901
+ if (this.status == "walking") {
902
+ this.el.setAttribute("static-body", this.staticBody);
903
+ this.el.setAttribute("shape__main", this.shapeMain);
904
+ } else {
905
+ this.el.setAttribute("dynamic-body", this.dynamicBody);
906
+ this.el.setAttribute("shape__main", this.shapeMain);
907
+ }
908
+ } else {
909
+ this.el.removeAttribute("dynamic-body");
910
+ this.el.removeAttribute("static-body");
911
+ this.el.removeAttribute("shape__main");
912
+ }
913
+ },
914
+
915
+ flip: function (entity) {
916
+ const position = entity.getAttribute("position");
917
+ position.x *= -1;
918
+ entity.setAttribute("position", position);
919
+ const rotation = entity.getAttribute("rotation");
920
+ rotation.y *= -1;
921
+ rotation.z *= -1;
922
+ entity.setAttribute("rotation", rotation);
923
+ },
924
+
925
+ // EYE CONTROLLER
926
+ eyeControllersRange: {
927
+ pitch: { min: -40, max: 40 },
928
+ yaw: { min: -40, max: 40 },
929
+ },
930
+
931
+ setEyeRotation: function (
932
+ side,
933
+ rotation,
934
+ dur = 50,
935
+ easing = "linear",
936
+ loop = false,
937
+ dir = "normal"
938
+ ) {
939
+ const entity = this.eyeControllers[side];
940
+ if (!entity) {
941
+ return;
942
+ }
943
+
944
+ let pitch = THREE.MathUtils.lerp(
945
+ this.eyeControllersRange.pitch.min,
946
+ this.eyeControllersRange.pitch.max,
947
+ rotation.pitch
948
+ );
949
+ let yaw = THREE.MathUtils.lerp(
950
+ this.eyeControllersRange.yaw.min,
951
+ this.eyeControllersRange.yaw.max,
952
+ rotation.yaw
953
+ );
954
+
955
+ this.clearEyeRotationAnimation(side);
956
+ if (dur == 0) {
957
+ pitch = THREE.MathUtils.degToRad(pitch);
958
+ yaw = THREE.MathUtils.degToRad(yaw);
959
+ entity.object3D.rotation.set(pitch, yaw, 0);
960
+ } else {
961
+ entity.setAttribute("animation__rot", {
962
+ property: "rotation",
963
+ to: `${pitch} ${yaw} 0`,
964
+ from: dir == "alternate" ? `${-pitch} ${-yaw} 0` : undefined,
965
+ dur: dur,
966
+ easing,
967
+ loop,
968
+ dir,
969
+ });
970
+ }
971
+ },
972
+ setEyesRotation: function (rotation, dur, easing, loop) {
973
+ this.sides.forEach((side) => {
974
+ this.setEyeRotation(side, ...arguments);
975
+ });
976
+ },
977
+ resetEyes: function () {
978
+ this.setEyesRotation({ pitch: 0.5, yaw: 0.5 });
979
+ this.setEyesRoll({ roll: 0.5 });
980
+ this.resetEyesScale();
981
+ },
982
+ clearEyeRotationAnimation: function (side) {
983
+ const entity = this.eyeControllers[side];
984
+ if (!entity) {
985
+ return;
986
+ }
987
+ entity.removeAttribute("animation__rot");
988
+ },
989
+ clearEyesRotationAnimation: function () {
990
+ this.sides.forEach((side) => {
991
+ this.clearEyeRotationAnimation(side);
992
+ });
993
+ },
994
+
995
+ // EYE SCALE
996
+ eyeScaleRange: {
997
+ width: { min: 0, max: 1 },
998
+ height: { min: 0, max: 1 },
999
+ },
1000
+ setEyeScale: async function (
1001
+ side,
1002
+ scale,
1003
+ dur = 50,
1004
+ easing = "linear",
1005
+ loop = false,
1006
+ dir = "normal",
1007
+ wholeEye = false
1008
+ ) {
1009
+ let entity = this.eyeScales[side];
1010
+ if (!entity) {
1011
+ return;
1012
+ }
1013
+ if (scale.width == undefined) {
1014
+ scale.width = 1;
1015
+ }
1016
+ if (scale.height == undefined) {
1017
+ scale.height = 1;
1018
+ }
1019
+ let width = 1;
1020
+ let height = 1;
1021
+ if (wholeEye) {
1022
+ entity = entity.closest(".eye");
1023
+ width = scale.width;
1024
+ height = scale.height;
1025
+ } else {
1026
+ width = THREE.MathUtils.lerp(
1027
+ this.eyeScaleRange.width.min,
1028
+ this.eyeScaleRange.width.max,
1029
+ scale.width
1030
+ );
1031
+ height = THREE.MathUtils.lerp(
1032
+ this.eyeScaleRange.height.min,
1033
+ this.eyeScaleRange.height.max,
1034
+ scale.height
1035
+ );
1036
+ }
1037
+
1038
+ this.clearEyeScaleAnimation(side, wholeEye);
1039
+ if (dur == 0) {
1040
+ entity.object3D.scale.set(width, height, 1);
1041
+ } else {
1042
+ entity.setAttribute("animation__scale", {
1043
+ property: "scale",
1044
+ to: `${width} ${height} 1`,
1045
+ from:
1046
+ dir == "alternate"
1047
+ ? `${this.eyesScales[side].width} ${this.eyesScales[side].height} 1`
1048
+ : undefined,
1049
+ dur: dur,
1050
+ easing,
1051
+ loop,
1052
+ dir,
1053
+ });
1054
+ return new Promise((resolve) => {
1055
+ entity.addEventListener("animationcomplete__scale", () => {
1056
+ resolve();
1057
+ });
1058
+ });
1059
+ }
1060
+ },
1061
+ setEyesScale: function (scale, dur, easing, loop, dir, wholeEye) {
1062
+ this.sides.forEach((side) => {
1063
+ this.setEyeScale(side, ...arguments);
1064
+ });
1065
+ },
1066
+
1067
+ setWhiteEyeRoll: function (
1068
+ side,
1069
+ { roll },
1070
+ options = { dur: 100, easing: "easeInOutQuad" }
1071
+ ) {
1072
+ if (side == "left") {
1073
+ roll *= -1;
1074
+ }
1075
+ const eyeWhite = this.eyeWhites[side];
1076
+ eyeWhite.removeAttribute("animation__roll");
1077
+ eyeWhite.setAttribute("animation__roll", {
1078
+ property: "rotation",
1079
+ to: `0 0 ${roll}`,
1080
+ ...options,
1081
+ });
1082
+ },
1083
+ setWhiteEyesRoll: function () {
1084
+ this.sides.forEach((side) => {
1085
+ this.setWhiteEyeRoll(side, ...arguments);
1086
+ });
1087
+ },
1088
+
1089
+ setEyelid: function (
1090
+ side,
1091
+ { upper, lower },
1092
+ options = { dur: 100, easing: "easeInOutQuad" }
1093
+ ) {
1094
+ lower = 1 - lower;
1095
+ lower = Math.max(lower, upper);
1096
+
1097
+ let eyeHeight = lower - upper; // [0, 1]
1098
+ const thetaLength = eyeHeight * 180;
1099
+
1100
+ let eyeYOffset = upper; // [0, 1]
1101
+ const thetaStart = eyeYOffset * 180;
1102
+
1103
+ const eyeWhite = this.eyeWhites[side];
1104
+
1105
+ eyeWhite.removeAttribute("animation__thetalength");
1106
+ eyeWhite.setAttribute("animation__thetalength", {
1107
+ property: "geometry.thetaLength",
1108
+ //from: eyeWhite.getAttribute("theta-length"),
1109
+ to: thetaLength.toString(),
1110
+ ...options,
1111
+ });
1112
+
1113
+ eyeWhite.removeAttribute("animation__thetastart");
1114
+ eyeWhite.setAttribute("animation__thetastart", {
1115
+ property: "geometry.thetaStart",
1116
+ // from: eyeWhite.getAttribute("theta-start"),
1117
+ to: thetaStart.toString(),
1118
+ ...options,
1119
+ });
1120
+ },
1121
+ setEyelids: function () {
1122
+ this.sides.forEach((side) => {
1123
+ this.setEyelid(side, ...arguments);
1124
+ });
1125
+ },
1126
+
1127
+ emotions: ["default", "happy", "angry", "sad"],
1128
+ setEmotion: function (newEmotion) {
1129
+ if (this.emotion == newEmotion) {
1130
+ return;
1131
+ }
1132
+ if (!this.emotions.includes(newEmotion)) {
1133
+ console.error(`invalid emotion "${newEmotion}"`);
1134
+ return;
1135
+ }
1136
+ this.emotion = newEmotion;
1137
+
1138
+ this.blink();
1139
+ switch (this.emotion) {
1140
+ case "happy":
1141
+ this.setEyelids({ upper: 0, lower: 0.3 });
1142
+ this.setWhiteEyesRoll({ roll: 8 });
1143
+ break;
1144
+ case "sad":
1145
+ this.setEyelids({ upper: 0.3, lower: 0.0 });
1146
+ this.setWhiteEyesRoll({ roll: -15 });
1147
+ break;
1148
+ case "angry":
1149
+ this.setEyelids({ upper: 0.3, lower: 0.2 });
1150
+ this.setWhiteEyesRoll({ roll: 12 });
1151
+ break;
1152
+ case "default":
1153
+ this.setEyelids({ upper: 0, lower: 0 });
1154
+ this.setWhiteEyesRoll({ roll: 0 });
1155
+ break;
1156
+ default:
1157
+ break;
1158
+ }
1159
+ },
1160
+
1161
+ resetEyesScale: function () {
1162
+ this.setEyeScale("left", this.eyesScales.left);
1163
+ this.setEyeScale("right", this.eyesScales.right);
1164
+ this.setEyesScale(
1165
+ { height: 1, width: 1 },
1166
+ 100,
1167
+ undefined,
1168
+ undefined,
1169
+ undefined,
1170
+ true
1171
+ );
1172
+ },
1173
+ clearEyeScaleAnimation: function (side, wholeEye = false) {
1174
+ let entity = this.eyeScales[side];
1175
+ if (!entity) {
1176
+ return;
1177
+ }
1178
+ if (wholeEye) {
1179
+ entity = entity.closest(".eye");
1180
+ }
1181
+ entity.removeAttribute("animation__scale");
1182
+ },
1183
+ clearEyesScaleAnimation: function () {
1184
+ this.sides.forEach((side) => {
1185
+ this.clearEyeScaleAnimation(side);
1186
+ });
1187
+ },
1188
+
1189
+ blinkIntervalRange: { min: 3000, max: 5000 },
1190
+ blink: async function (dur = 110, easing = "easeInBack", height = 0) {
1191
+ if (this.isBlinking) {
1192
+ return;
1193
+ }
1194
+ this.lastBlinkTick = this.latestTick;
1195
+ this.blinkInterval = THREE.MathUtils.lerp(
1196
+ this.blinkIntervalRange.min,
1197
+ this.blinkIntervalRange.max,
1198
+ Math.random()
1199
+ );
1200
+ this.isBlinking = true;
1201
+ const promises = this.sides.map(async (side) => {
1202
+ const firstSide = Math.round(Math.random()) ? "left" : "right";
1203
+ await this.setEyeScale(
1204
+ side,
1205
+ { height, width: 1 },
1206
+ side == firstSide ? dur : dur + 18,
1207
+ easing,
1208
+ 1,
1209
+ "alternate",
1210
+ true
1211
+ );
1212
+ });
1213
+ await Promise.all(promises);
1214
+ this.isBlinking = false;
1215
+ },
1216
+
1217
+ // EYE ROTATOR
1218
+ eyeRotatorsRange: {
1219
+ roll: { min: -50, max: 50 },
1220
+ },
1221
+ setEyeRoll: function (
1222
+ side,
1223
+ rotation,
1224
+ dur = 50,
1225
+ easing = "linear",
1226
+ loop = false,
1227
+ dir = "normal",
1228
+ invert = false
1229
+ ) {
1230
+ const entity = this.eyeRotators[side];
1231
+ if (!entity) {
1232
+ return;
1233
+ }
1234
+
1235
+ let roll = THREE.MathUtils.lerp(
1236
+ this.eyeRotatorsRange.roll.min,
1237
+ this.eyeRotatorsRange.roll.max,
1238
+ invert ? 1 - rotation.roll : rotation.roll
1239
+ );
1240
+
1241
+ this.clearEyeRollAnimation(side);
1242
+ if (dur == 0) {
1243
+ roll = THREE.MathUtils.degToRad(roll);
1244
+ entity.object3D.rotation.set(0, 0, roll);
1245
+ } else {
1246
+ entity.setAttribute("animation__rot", {
1247
+ property: "rotation",
1248
+ to: `0 0 ${roll}`,
1249
+ from: dir == "alternate" ? `0 0 ${-roll}` : undefined,
1250
+ dur: dur,
1251
+ easing,
1252
+ loop,
1253
+ dir,
1254
+ });
1255
+ }
1256
+ },
1257
+ setEyesRoll: async function (rotation, dur, easing, loop, dir, invert) {
1258
+ this.sides.forEach((side) => {
1259
+ if (invert) {
1260
+ const invert = side == "left";
1261
+ return this.setEyeRoll(side, rotation, dur, easing, loop, dir, invert);
1262
+ } else {
1263
+ return this.setEyeRoll(side, ...arguments);
1264
+ }
1265
+ });
1266
+ },
1267
+
1268
+ resetEyesRoll: function () {
1269
+ this.setEyesRoll({ roll: 0.5 });
1270
+ },
1271
+ clearEyeRollAnimation: function (side) {
1272
+ const entity = this.eyeRotators[side];
1273
+ if (!entity) {
1274
+ return;
1275
+ }
1276
+ entity.removeAttribute("animation__rot");
1277
+ },
1278
+ clearEyesRollAnimation: function () {
1279
+ this.sides.forEach((side) => {
1280
+ this.clearEyeRollAnimation(side);
1281
+ });
1282
+ },
1283
+
1284
+ // LookAt
1285
+
1286
+ lookAtRefocusScalar: 0.025,
1287
+ lookAtRefocusScalarRange: { min: 0.0, max: 0.02 },
1288
+
1289
+ eyeRefocusIntervalRange: { min: 100, max: 800 },
1290
+ wanderEyesIntervalRange: { min: 750, max: 2300 },
1291
+ wanderRefocusScalar: 3,
1292
+ pointToLookAtAngleThreshold: THREE.MathUtils.degToRad(60),
1293
+
1294
+ wanderEyesEulerRange: {
1295
+ pitch: { min: -40, max: 10 },
1296
+ yaw: { min: -50, max: 50 },
1297
+ },
1298
+
1299
+ lookAt: function (position, refocus = false, refocusScalar = 1) {
1300
+ this.lookAtPosition.copy(position);
1301
+ if (refocus) {
1302
+ if (true) {
1303
+ // this.el.object3D.getWorldQuaternion(this.worldQuaternion);
1304
+
1305
+ const intervalInterpolation = THREE.MathUtils.inverseLerp(
1306
+ 0,
1307
+ this.eyeRefocusIntervalRange.max,
1308
+ this.eyeRefocusInterval
1309
+ );
1310
+ const scalar = refocusScalar * (1 - intervalInterpolation);
1311
+
1312
+ const randomX =
1313
+ THREE.MathUtils.lerp(
1314
+ -this.lookAtRefocusScalar,
1315
+ this.lookAtRefocusScalar,
1316
+ Math.random()
1317
+ ) * scalar;
1318
+ const randomY =
1319
+ THREE.MathUtils.lerp(
1320
+ -this.lookAtRefocusScalar,
1321
+ this.lookAtRefocusScalar,
1322
+ Math.random()
1323
+ ) * scalar;
1324
+
1325
+ this.lookAtRefocusVector.set(randomX, randomY, 0);
1326
+ this.lookAtRefocusVector.applyQuaternion(this.worldQuaternion);
1327
+
1328
+ if (false) {
1329
+ const entity = this.squash;
1330
+ this.squashLookAtQuaternion1.copy(entity.object3D.quaternion);
1331
+ this.tempLookAtEuler.copy(entity.object3D.rotation);
1332
+ entity.object3D.lookAt(this.lookAtPosition);
1333
+ this.squashLookAtQuaternion2.copy(entity.object3D.quaternion);
1334
+ entity.object3D.rotation.copy(this.tempLookAtEuler);
1335
+ this.squashLookAtStartTime = this.latestTick;
1336
+ this.isRotatingSquash = true;
1337
+ }
1338
+ } else {
1339
+ const scalar = THREE.MathUtils.lerp(
1340
+ this.lookAtRefocusScalarRange.min,
1341
+ this.lookAtRefocusScalarRange.max,
1342
+ Math.random()
1343
+ );
1344
+ this.lookAtRefocusVector.set(scalar, 0, 0);
1345
+ this.lookAtRefocusAxis
1346
+ .set(Math.random(), Math.random(), Math.random())
1347
+ .normalize();
1348
+ const angle = Math.random() * Math.PI * 2;
1349
+ this.lookAtRefocusVector.applyAxisAngle(this.lookAtRefocusAxis, angle);
1350
+ }
1351
+
1352
+ this.lookAtPosition.add(this.lookAtRefocusVector);
1353
+ }
1354
+ this.sides.forEach((side) => {
1355
+ const entity = this.eyeControllers[side];
1356
+ if (true) {
1357
+ this.tempLookAtEuler.copy(entity.object3D.rotation);
1358
+ entity.object3D.lookAt(this.lookAtPosition);
1359
+ this.lookAtEuler.copy(entity.object3D.rotation);
1360
+ this.lookAtEuler.z = 0;
1361
+ entity.object3D.rotation.copy(this.tempLookAtEuler);
1362
+ entity.setAttribute("animation__rot", {
1363
+ property: "rotation",
1364
+ to: this.lookAtEuler
1365
+ .toArray()
1366
+ .slice(0, 3)
1367
+ .map((value) => THREE.MathUtils.radToDeg(value))
1368
+ .join(" "),
1369
+ dur: 100,
1370
+ easing: "easeOutCubic",
1371
+ });
1372
+ } else {
1373
+ entity.object3D.lookAt(this.lookAtPosition);
1374
+ }
1375
+ });
1376
+ },
1377
+ lookAtObject: function (object) {
1378
+ if (object == this.objectToLookAt) {
1379
+ return;
1380
+ }
1381
+
1382
+ this.objectToLookAt = object;
1383
+ if (!this.objectToLookAt) {
1384
+ this.resetEyes();
1385
+ }
1386
+ },
1387
+ stopLookingAtObject: function () {
1388
+ this.objectToLookAt = undefined;
1389
+ },
1390
+
1391
+ distanceRange: { min: 0.15, max: 5 },
1392
+ angleThreshold: 0.75,
1393
+
1394
+ angleThresholds: {
1395
+ pitch: { min: -0.1, max: 0.7 },
1396
+ yaw: { min: -0.7, max: 0.7 },
1397
+ },
1398
+
1399
+ checkObjectToLookAt: function () {
1400
+ let closestEntity;
1401
+ let closestDistance = 1;
1402
+ this.forwardVector
1403
+ .set(0, 0, 1)
1404
+ .applyQuaternion(this.worldQuaternion)
1405
+ .normalize();
1406
+ this.upVector
1407
+ .set(0, 1, 0)
1408
+ .applyQuaternion(this.worldQuaternion)
1409
+ .normalize();
1410
+ this.rightVector
1411
+ .set(1, 0, 0)
1412
+ .applyQuaternion(this.worldQuaternion)
1413
+ .normalize();
1414
+ this.lookAtTargets.forEach((entity) => {
1415
+ if (!entity.object3D.visible) {
1416
+ return;
1417
+ }
1418
+ if (!entity.isPlaying) {
1419
+ return;
1420
+ }
1421
+ if (entity.components["hand-tracking-controls"]) {
1422
+ if (!entity.components["hand-tracking-controls"].controllerPresent) {
1423
+ return;
1424
+ }
1425
+ this.otherWorldPosition.copy(
1426
+ entity.components["hand-tracking-controls"].indexTipPosition
1427
+ );
1428
+ } else {
1429
+ entity.object3D.getWorldPosition(this.otherWorldPosition);
1430
+ }
1431
+
1432
+ if (this.isLockedToCamera) {
1433
+ if (
1434
+ entity != this.camera &&
1435
+ !entity.components["hand-tracking-controls"]
1436
+ ) {
1437
+ return;
1438
+ }
1439
+ }
1440
+
1441
+ this.toVector.subVectors(this.otherWorldPosition, this.worldPosition);
1442
+
1443
+ const distance = this.toVector.length();
1444
+ if (
1445
+ distance < this.distanceRange.min ||
1446
+ distance > this.distanceRange.max
1447
+ ) {
1448
+ return;
1449
+ }
1450
+ if (distance > closestDistance) {
1451
+ return;
1452
+ }
1453
+
1454
+ this.toVector.normalize();
1455
+
1456
+ const angle = this.forwardVector.dot(this.toVector);
1457
+ if (angle < this.angleThreshold) {
1458
+ return;
1459
+ }
1460
+
1461
+ const cosYaw = THREE.MathUtils.clamp(
1462
+ this.rightVector.dot(this.toVector),
1463
+ -1,
1464
+ 1
1465
+ );
1466
+ let yaw =
1467
+ Math.sign(this.forwardVector.dot(this.toVector)) >= 0
1468
+ ? Math.acos(cosYaw) // point is above the horizon
1469
+ : -Math.acos(cosYaw); // below – give negative yaw
1470
+ yaw -= Math.PI / 2;
1471
+
1472
+ const pitch = Math.asin(this.upVector.dot(this.toVector)); // between −π/2 and π/2
1473
+
1474
+ if (entity.components["hand-tracking-controls"]) {
1475
+ if (pitch < 0.01) {
1476
+ return;
1477
+ }
1478
+ }
1479
+
1480
+ if (
1481
+ pitch < this.angleThresholds.pitch.min ||
1482
+ pitch > this.angleThresholds.pitch.max
1483
+ ) {
1484
+ return;
1485
+ }
1486
+ if (
1487
+ yaw < this.angleThresholds.yaw.min ||
1488
+ yaw > this.angleThresholds.yaw.max
1489
+ ) {
1490
+ return;
1491
+ }
1492
+
1493
+ closestEntity = entity;
1494
+ closestDistance = distance;
1495
+ });
1496
+ this.objectToLookAtDistance = closestDistance;
1497
+ this.lookAtObject(closestEntity);
1498
+ },
1499
+
1500
+ // LEG
1501
+ legRotationRange: {
1502
+ pitch: { min: 50, max: -50 },
1503
+ roll: { min: 50, max: -50 },
1504
+ yaw: { min: 50, max: -50 },
1505
+ },
1506
+
1507
+ setLegRotation: async function (
1508
+ side,
1509
+ rotation,
1510
+ dur = 50,
1511
+ easing = "linear",
1512
+ loop = false,
1513
+ dir = "normal",
1514
+ invert = false,
1515
+ isFoot = false
1516
+ ) {
1517
+ let entity = this.legs[side];
1518
+ if (!entity) {
1519
+ return;
1520
+ }
1521
+ if (isFoot) {
1522
+ entity = this.feet[side];
1523
+ }
1524
+
1525
+ let pitch = THREE.MathUtils.lerp(
1526
+ this.legRotationRange.pitch.min,
1527
+ this.legRotationRange.pitch.max,
1528
+ invert ? 1 - rotation.pitch : rotation.pitch
1529
+ );
1530
+ let pitch2;
1531
+ if (rotation.pitch2 != undefined) {
1532
+ pitch2 = THREE.MathUtils.lerp(
1533
+ this.legRotationRange.pitch.min,
1534
+ this.legRotationRange.pitch.max,
1535
+ invert ? 1 - rotation.pitch2 : rotation.pitch2
1536
+ );
1537
+ }
1538
+
1539
+ if (rotation.yaw == undefined) {
1540
+ rotation.yaw = 0.5;
1541
+ }
1542
+ let yaw = THREE.MathUtils.lerp(
1543
+ this.legRotationRange.yaw.min,
1544
+ this.legRotationRange.yaw.max,
1545
+ invert ? 1 - rotation.yaw : rotation.yaw
1546
+ );
1547
+ let yaw2 = 0;
1548
+ if (rotation.yaw2 != undefined) {
1549
+ yaw2 = THREE.MathUtils.lerp(
1550
+ this.legRotationRange.yaw.min,
1551
+ this.legRotationRange.yaw.max,
1552
+ invert ? 1 - rotation.yaw2 : rotation.yaw2
1553
+ );
1554
+ }
1555
+
1556
+ if (rotation.roll == undefined) {
1557
+ rotation.roll = 0.5;
1558
+ }
1559
+ let roll = THREE.MathUtils.lerp(
1560
+ this.legRotationRange.roll.min,
1561
+ this.legRotationRange.roll.max,
1562
+ invert ? 1 - rotation.roll : rotation.roll
1563
+ );
1564
+ let roll2 = 0;
1565
+ if (rotation.roll2 != undefined) {
1566
+ roll2 = THREE.MathUtils.lerp(
1567
+ this.legRotationRange.roll.min,
1568
+ this.legRotationRange.roll.max,
1569
+ invert ? 1 - rotation.roll2 : rotation.roll2
1570
+ );
1571
+ }
1572
+
1573
+ this.clearLegRotationAnimation(side, isFoot);
1574
+ if (dur == 0) {
1575
+ pitch = THREE.MathUtils.degToRad(pitch);
1576
+ yaw = THREE.MathUtils.degToRad(yaw);
1577
+ roll = THREE.MathUtils.degToRad(roll);
1578
+ entity.object3D.rotation.set(pitch, yaw, roll);
1579
+ } else {
1580
+ const options = {
1581
+ property: "rotation",
1582
+ to: `${pitch} ${yaw} ${roll}`,
1583
+ from: dir == "alternate" ? `${-pitch} ${-yaw} ${-roll}` : undefined,
1584
+ dur: dur,
1585
+ dir: dir,
1586
+ easing,
1587
+ loop,
1588
+ };
1589
+ if (pitch2 != undefined) {
1590
+ options.from = `${pitch2} ${yaw2} ${roll2}`;
1591
+ }
1592
+ entity.setAttribute("animation__rot", options);
1593
+ return new Promise((resolve) => {
1594
+ entity.addEventListener("animationcomplete__rot", () => {
1595
+ resolve();
1596
+ });
1597
+ });
1598
+ }
1599
+ },
1600
+ setLegsRotation: async function (
1601
+ rotation,
1602
+ dur,
1603
+ easing,
1604
+ loop,
1605
+ dir,
1606
+ invert = false,
1607
+ isFoot = false
1608
+ ) {
1609
+ const promises = this.sides.map((side) => {
1610
+ if (invert) {
1611
+ const invert = side == "left";
1612
+ return this.setLegRotation(
1613
+ side,
1614
+ rotation,
1615
+ dur,
1616
+ easing,
1617
+ loop,
1618
+ dir,
1619
+ invert,
1620
+ isFoot
1621
+ );
1622
+ } else {
1623
+ return this.setLegRotation(side, ...arguments);
1624
+ }
1625
+ });
1626
+ const x = Date.now();
1627
+ await Promise.all(promises);
1628
+ },
1629
+
1630
+ resetLegs: function () {
1631
+ this.setLegsRotation({ pitch: 0.5 });
1632
+ this.setLegsRotation(
1633
+ { pitch: 0.5 },
1634
+ undefined,
1635
+ undefined,
1636
+ undefined,
1637
+ undefined,
1638
+ false,
1639
+ true
1640
+ );
1641
+ },
1642
+ clearLegRotationAnimation: function (side, isFoot) {
1643
+ let entity = this.legs[side];
1644
+ if (!entity) {
1645
+ return;
1646
+ }
1647
+ if (isFoot) {
1648
+ entity = this.feet[side];
1649
+ }
1650
+ entity.removeAttribute("animation__rot");
1651
+ },
1652
+ clearLegsRotationAnimation: function (isFoot = true) {
1653
+ this.sides.forEach((side) => {
1654
+ this.clearLegRotationAnimation(side, isFoot);
1655
+ });
1656
+ },
1657
+
1658
+ squashLookAtInterval: 300,
1659
+
1660
+ walkInterval: 100,
1661
+ walkSpeed: 0.0001,
1662
+ walkIntervalRange: { min: 100, max: 1200 },
1663
+ walkAngleRange: { min: -30, max: 30 },
1664
+
1665
+ idleToWalkingTime: 1000,
1666
+
1667
+ petInterval: { short: 50, long: 50 },
1668
+ petDistanceThreshold: { start: 0.03, end: 0.15 },
1669
+ petSquashRange: { min: -0.04, max: 0.02 },
1670
+ petSquashYRange: { min: 0.65, max: 1 },
1671
+ petSquashXZRange: { min: 1, max: 1.1 },
1672
+ petSquashEyesHeightRange: { min: 0.1, max: 1 },
1673
+ petSquashEyesHeightBiasRange: { min: -0.2, max: 0.2 },
1674
+ petSquashEyesRollRange: { min: 0.4, max: 0.6 },
1675
+ petEyeSquashRange: { min: -0.03, max: 0.01 },
1676
+
1677
+ petSquashPurrRange: { min: 1, max: 0.08 },
1678
+
1679
+ petBodyPitchRange: { min: -0.05, max: 0.05 },
1680
+ petSquashBodyPitchRange: { min: -10, max: 5 },
1681
+
1682
+ petBodyRollRange: { min: -0.05, max: 0.05 },
1683
+ petSquashBodyRollRange: { min: 5, max: -5 },
1684
+
1685
+ tick: function (time, timeDelta) {
1686
+ this.el.object3D.getWorldPosition(this.worldPosition);
1687
+ this.el.object3D.getWorldQuaternion(this.worldQuaternion);
1688
+ if (this.shouldDie) {
1689
+ this.el.emit("die");
1690
+ this.el.remove();
1691
+ const squashedGoomba =
1692
+ this.el.sceneEl.components["pool__squashedgoomba"].requestEntity();
1693
+ squashedGoomba.play();
1694
+ const position = new THREE.Vector3();
1695
+ // console.log(this.deathCollidedEntity);
1696
+ if (false) {
1697
+ this.deathCollidedEntity.components["obb-collider"].obb.clampPoint(
1698
+ this.worldPosition,
1699
+ position
1700
+ );
1701
+ } else {
1702
+ position.copy(this.worldPosition);
1703
+
1704
+ if (false) {
1705
+ const ray = new THREE.Ray(this.worldPosition, this.deathVelocity);
1706
+ this.deathCollidedEntity.components["obb-collider"].aabb.intersectRay(
1707
+ ray,
1708
+ position
1709
+ );
1710
+ } else {
1711
+ this.ray.copy(this.deathVelocity);
1712
+ this.raycaster.set(this.worldPosition, this.ray);
1713
+ this.raycaster.near = 0;
1714
+ this.raycaster.far = 1;
1715
+ let intersectable =
1716
+ this.deathCollidedEntity.components["occlude-mesh"]?.raycastMesh;
1717
+ // console.log("intersectable", intersectable);
1718
+ const intersections = this.raycaster.intersectObjects(
1719
+ intersectable ? [intersectable] : this.lookAtRaycastTargetObjects,
1720
+ true
1721
+ );
1722
+ if (intersections[0]) {
1723
+ position.copy(intersections[0].point);
1724
+ } else {
1725
+ // console.log("no intersections found for death punch");
1726
+ }
1727
+ }
1728
+ }
1729
+ squashedGoomba.object3D.position.copy(position);
1730
+ {
1731
+ const { x, y, z } = this.deathNormal;
1732
+
1733
+ const yaw = Math.atan2(x, z);
1734
+ const pitch = Math.atan2(y, Math.sqrt(x * x + z * z));
1735
+ // console.log("death", { yaw, pitch });
1736
+ squashedGoomba.object3D.rotation.set(-pitch, yaw, 0, "YXZ");
1737
+ }
1738
+ squashedGoomba.components["squashed-goomba"].start();
1739
+ return;
1740
+ }
1741
+ this.sphere.center.copy(this.worldPosition);
1742
+
1743
+ if (this.shelled && this.status == "falling") {
1744
+ if (this.physicsBody.velocity.y < 0) {
1745
+ console.log("die");
1746
+
1747
+ const coin = this.el.sceneEl.components["pool__coin"].requestEntity();
1748
+ coin.play();
1749
+ {
1750
+ const cameraToCoin = new THREE.Vector3().subVectors(
1751
+ this.camera.object3D.position,
1752
+ this.el.object3D.position
1753
+ );
1754
+ const yaw = Math.atan2(cameraToCoin.x, cameraToCoin.z);
1755
+ coin.object3D.rotation.set(0, yaw, 0, "YXZ");
1756
+ console.log("rotation", coin.object3D.rotation);
1757
+ }
1758
+ {
1759
+ const { x, y, z } = this.el.object3D.position;
1760
+ const position = new THREE.Vector3(x, y - 0.04, z);
1761
+ coin.object3D.position.copy(position);
1762
+ console.log("position", coin.object3D.position);
1763
+ }
1764
+ console.log("coin", coin);
1765
+ coin.components.coin.start();
1766
+
1767
+ this.el.emit("die");
1768
+ this.el.remove();
1769
+ }
1770
+ }
1771
+
1772
+ if (this.punchOptions) {
1773
+ const { velocity, position } = this.punchOptions;
1774
+ this.punchOptions = undefined;
1775
+ this._punch(velocity, position);
1776
+ }
1777
+ if (this.shellHitOptions) {
1778
+ const { velocity, position } = this.shellHitOptions;
1779
+ this.shellHitOptions = undefined;
1780
+ this._shellHit(velocity, position);
1781
+ }
1782
+ if (this.stompOptions) {
1783
+ const { distance, yaw, kill } = this.stompOptions;
1784
+ this.stompOptions = undefined;
1785
+ this._stomp(distance, yaw, kill);
1786
+ }
1787
+
1788
+ if (this.punchSqueakSound) {
1789
+ this.punchSqueakSound.object3D.position.copy(this.worldPosition);
1790
+ }
1791
+
1792
+ if ("updatePhysicsEnabledFlag" in this) {
1793
+ const enabled = this.updatePhysicsEnabledFlag;
1794
+ this.updatePhysicsEnabled(enabled);
1795
+ delete this.updatePhysicsEnabledFlag;
1796
+ }
1797
+
1798
+ this.latestTick = time;
1799
+
1800
+ if (this.latestTick - this.lastTimePunched < 10) {
1801
+ return;
1802
+ }
1803
+
1804
+ if (this.status == "falling") {
1805
+ if (this.worldPosition.y < -40) {
1806
+ console.log("fell through floor");
1807
+ this.el.remove();
1808
+ }
1809
+ }
1810
+
1811
+ if (
1812
+ this.status == "idle" ||
1813
+ this.status == "petting" ||
1814
+ this.status == "walking"
1815
+ ) {
1816
+ if (
1817
+ this.latestTick - this.lastPetTick >
1818
+ (this.status == "petting"
1819
+ ? this.petInterval.short
1820
+ : this.petInterval.long)
1821
+ ) {
1822
+ this.lastPetTick = this.latestTick;
1823
+ if (this.status == "idle" || this.status == "walking") {
1824
+ for (const side in this.hands) {
1825
+ const hand = this.hands[side];
1826
+ if (hand?.controllerPresent) {
1827
+ this.petEntity.object3D.getWorldPosition(this.petPosition);
1828
+ const distance = hand.indexTipPosition.distanceTo(
1829
+ this.petPosition
1830
+ );
1831
+ // console.log({ side, distance });
1832
+ if (distance < this.petDistanceThreshold.start) {
1833
+ this.petSide = side;
1834
+ this.setStatus("petting");
1835
+ this.isLockedToCamera = true;
1836
+ this.lookAtObject(this.camera);
1837
+ break;
1838
+ }
1839
+ }
1840
+ }
1841
+ }
1842
+ if (this.status == "petting") {
1843
+ const hand = this.hands[this.petSide];
1844
+ if (hand?.controllerPresent) {
1845
+ this.petEntity.object3D.getWorldPosition(this.petPosition);
1846
+ const distance = hand.indexTipPosition.distanceTo(this.petPosition);
1847
+ // console.log({ side, distance });
1848
+ if (distance > this.petDistanceThreshold.end) {
1849
+ this.setStatus("idle");
1850
+ } else {
1851
+ const easing = "linear";
1852
+ const dur = 40;
1853
+
1854
+ this.localPetPosition.copy(hand.indexTipPosition);
1855
+ this.petEntity.object3D.worldToLocal(this.localPetPosition);
1856
+
1857
+ let squashInterpolation = THREE.MathUtils.inverseLerp(
1858
+ this.petSquashRange.min,
1859
+ this.petSquashRange.max,
1860
+ this.localPetPosition.y
1861
+ );
1862
+ let clampedSquashInterpolation = THREE.MathUtils.clamp(
1863
+ squashInterpolation,
1864
+ 0,
1865
+ 1
1866
+ );
1867
+ if (this.purrSound) {
1868
+ const purrSoundInterpolation = THREE.MathUtils.lerp(
1869
+ this.petSquashPurrRange.min,
1870
+ this.petSquashPurrRange.max,
1871
+ clampedSquashInterpolation
1872
+ );
1873
+ this.latestPurrVolume = purrSoundInterpolation;
1874
+ this.purrSound.components.sound.pool.children[0].setVolume(
1875
+ purrSoundInterpolation
1876
+ );
1877
+ }
1878
+ const squashInterpolationY = THREE.MathUtils.lerp(
1879
+ this.petSquashYRange.min,
1880
+ this.petSquashYRange.max,
1881
+ clampedSquashInterpolation
1882
+ );
1883
+ const squashInterpolationXZ = THREE.MathUtils.lerp(
1884
+ this.petSquashXZRange.min,
1885
+ this.petSquashXZRange.max,
1886
+ 1 - clampedSquashInterpolation
1887
+ );
1888
+
1889
+ if (true) {
1890
+ this.squash.removeAttribute("animation__scale");
1891
+ this.squash.setAttribute("animation__scale", {
1892
+ property: "scale",
1893
+ to: `${squashInterpolationXZ} ${squashInterpolationY} ${squashInterpolationXZ}`,
1894
+ dur: dur,
1895
+ easing: easing,
1896
+ });
1897
+ } else {
1898
+ this.squash.object3D.scale.y = squashInterpolationY;
1899
+ }
1900
+
1901
+ squashInterpolation = THREE.MathUtils.inverseLerp(
1902
+ this.petEyeSquashRange.min,
1903
+ this.petEyeSquashRange.max,
1904
+ this.localPetPosition.y
1905
+ );
1906
+ clampedSquashInterpolation = THREE.MathUtils.clamp(
1907
+ squashInterpolation,
1908
+ 0,
1909
+ 1
1910
+ );
1911
+ const eyesScaleHeight = THREE.MathUtils.lerp(
1912
+ this.petSquashEyesHeightRange.min,
1913
+ this.petSquashEyesHeightRange.max,
1914
+ clampedSquashInterpolation
1915
+ );
1916
+
1917
+ squashInterpolation = THREE.MathUtils.inverseLerp(
1918
+ this.petBodyPitchRange.min,
1919
+ this.petBodyPitchRange.max,
1920
+ this.localPetPosition.z
1921
+ );
1922
+ clampedSquashInterpolation = THREE.MathUtils.clamp(
1923
+ squashInterpolation,
1924
+ 0,
1925
+ 1
1926
+ );
1927
+ const bodyPitch = THREE.MathUtils.lerp(
1928
+ this.petSquashBodyPitchRange.min,
1929
+ this.petSquashBodyPitchRange.max,
1930
+ clampedSquashInterpolation
1931
+ );
1932
+
1933
+ squashInterpolation = THREE.MathUtils.inverseLerp(
1934
+ this.petBodyRollRange.min,
1935
+ this.petBodyRollRange.max,
1936
+ this.localPetPosition.x
1937
+ );
1938
+ clampedSquashInterpolation = THREE.MathUtils.clamp(
1939
+ squashInterpolation,
1940
+ 0,
1941
+ 1
1942
+ );
1943
+ const bodyRoll = THREE.MathUtils.lerp(
1944
+ this.petSquashBodyRollRange.min,
1945
+ this.petSquashBodyRollRange.max,
1946
+ clampedSquashInterpolation
1947
+ );
1948
+
1949
+ if (true) {
1950
+ this.squash.removeAttribute("animation__rot");
1951
+ this.squash.setAttribute("animation__rot", {
1952
+ property: "rotation",
1953
+ to: `${bodyPitch} 0 ${bodyRoll}`,
1954
+ dur: dur,
1955
+ easing: easing,
1956
+ });
1957
+ } else {
1958
+ this.squash.object3D.rotation.x =
1959
+ THREE.MathUtils.degToRad(bodyPitch);
1960
+ this.squash.object3D.rotation.z =
1961
+ THREE.MathUtils.degToRad(bodyRoll);
1962
+ }
1963
+
1964
+ if (true) {
1965
+ const dominantSide = bodyRoll > 0 ? "left" : "right";
1966
+ const otherSide = dominantSide == "left" ? "right" : "left";
1967
+
1968
+ let rollEyeHeightBias = THREE.MathUtils.lerp(
1969
+ this.petSquashEyesHeightBiasRange.min,
1970
+ this.petSquashEyesHeightBiasRange.max,
1971
+ clampedSquashInterpolation
1972
+ );
1973
+ rollEyeHeightBias = Math.abs(rollEyeHeightBias);
1974
+
1975
+ this.setEyeScale(
1976
+ dominantSide,
1977
+ { height: eyesScaleHeight, width: 1 },
1978
+ dur,
1979
+ easing,
1980
+ undefined,
1981
+ undefined,
1982
+ true
1983
+ );
1984
+ this.setEyeScale(
1985
+ otherSide,
1986
+ {
1987
+ height: THREE.MathUtils.clamp(
1988
+ eyesScaleHeight + rollEyeHeightBias,
1989
+ 0,
1990
+ 1
1991
+ ),
1992
+ width: 1,
1993
+ },
1994
+ dur,
1995
+ easing,
1996
+ undefined,
1997
+ undefined,
1998
+ true
1999
+ );
2000
+ } else {
2001
+ this.setEyesScale(
2002
+ { height: eyesScaleHeight, width: 1 },
2003
+ dur,
2004
+ easing,
2005
+ undefined,
2006
+ undefined,
2007
+ true
2008
+ );
2009
+ }
2010
+
2011
+ if (false) {
2012
+ const eyesRoll = THREE.MathUtils.lerp(
2013
+ this.petSquashEyesRollRange.min,
2014
+ this.petSquashEyesRollRange.max,
2015
+ clampedSquashInterpolation
2016
+ );
2017
+ this.setEyesRoll(
2018
+ { roll: eyesRoll },
2019
+ dur,
2020
+ easing,
2021
+ false,
2022
+ undefined,
2023
+ true
2024
+ );
2025
+ }
2026
+ }
2027
+ } else {
2028
+ this.setStatus("idle");
2029
+ }
2030
+ }
2031
+ }
2032
+ }
2033
+
2034
+ if (this.status == "walking" && this.floor) {
2035
+ if (!this.slowDown) {
2036
+ this.slowDown =
2037
+ this.slowDown ||
2038
+ (this.objectToLookAt == this.camera && this.isLookedAt);
2039
+ if (this.slowDown) {
2040
+ //this.walkInterval *= 2;
2041
+ }
2042
+ }
2043
+
2044
+ if (time - this.lastWalkTick > this.walkInterval) {
2045
+ this.lastWalkTick = time;
2046
+ this.walkInterval = THREE.MathUtils.lerp(
2047
+ this.walkIntervalRange.min,
2048
+ this.walkIntervalRange.max,
2049
+ Math.random()
2050
+ );
2051
+
2052
+ let angleOffset = 0;
2053
+
2054
+ angleOffset = THREE.MathUtils.lerp(
2055
+ this.walkAngleRange.min,
2056
+ this.walkAngleRange.max,
2057
+ Math.random()
2058
+ );
2059
+
2060
+ let isAngleRelative = true;
2061
+ if (this.collidedWhenWalking) {
2062
+ if (this.debugColors) {
2063
+ this.body.setAttribute("color", "#a14e00");
2064
+ }
2065
+
2066
+ this.collidedWhenWalking = false;
2067
+ this.lastTimeCollidedWhenWalking = this.latestTick;
2068
+ if (this.collidedNewAngle != undefined) {
2069
+ isAngleRelative = false;
2070
+ angleOffset = this.collidedNewAngle;
2071
+ this.collidedNewAngle = undefined;
2072
+ } else {
2073
+ angleOffset = 180;
2074
+ }
2075
+ this.walkInterval = 300;
2076
+ // console.log("collided angleOffset", angleOffset);
2077
+ }
2078
+
2079
+ const distance = this.walkInterval * this.walkSpeed;
2080
+
2081
+ let attempts = -1;
2082
+ const turnSections = 12;
2083
+ const turnAngle = 360 / turnSections;
2084
+ const turnScalar = Math.round(Math.random()) ? 1 : -1;
2085
+ let didIntersectGoomba = false;
2086
+ let didIntersectMesh = false;
2087
+ let didIntersectWall = false;
2088
+ this.tempPointToWalkTo2 =
2089
+ this.tempPointToWalkTo2 || new THREE.Vector3();
2090
+ this.pointToWalkToOffset2 =
2091
+ this.pointToWalkToOffset2 || new THREE.Vector3();
2092
+
2093
+ do {
2094
+ attempts++;
2095
+ let angle = turnAngle * turnScalar * attempts + angleOffset;
2096
+ let shouldBreak = false;
2097
+ if (attempts == turnSections) {
2098
+ angle = 180;
2099
+ isAngleRelative = true;
2100
+ shouldBreak = true;
2101
+ }
2102
+ this.pointToWalkToOffset.set(0, 0, distance);
2103
+ this.pointToWalkToOffset.applyAxisAngle(
2104
+ this.worldBasis.up,
2105
+ THREE.MathUtils.degToRad(angle)
2106
+ );
2107
+ if (isAngleRelative) {
2108
+ this.pointToWalkToOffset.applyQuaternion(this.worldQuaternion);
2109
+ }
2110
+ // console.log({ attempts });
2111
+
2112
+ this.tempPointToWalkTo.copy(this.worldPosition);
2113
+ this.tempPointToWalkTo.add(this.pointToWalkToOffset);
2114
+ this.pointToWalkToOffset2
2115
+ .copy(this.pointToWalkToOffset)
2116
+ .setLength(0.25);
2117
+ this.tempPointToWalkTo2
2118
+ .copy(this.worldPosition)
2119
+ .add(this.pointToWalkToOffset2);
2120
+ this.floor.components["my-obb-collider"].obb.clampPoint(
2121
+ this.tempPointToWalkTo2,
2122
+ this.clampedTempPointToWalkTo
2123
+ );
2124
+ if (shouldBreak) {
2125
+ break;
2126
+ }
2127
+
2128
+ didIntersectGoomba = this.doesPointIntersectAnyGoombas(
2129
+ this.tempPointToWalkTo2
2130
+ );
2131
+ if (!didIntersectGoomba) {
2132
+ didIntersectWall = this.doesDirectionIntersectAnyWalls(
2133
+ this.pointToWalkToOffset.clone().normalize()
2134
+ );
2135
+ }
2136
+ if (!didIntersectGoomba && !didIntersectWall) {
2137
+ didIntersectMesh = this.doesPointIntersectAnyMeshes(
2138
+ this.tempPointToWalkTo2
2139
+ );
2140
+ }
2141
+ } while (
2142
+ didIntersectWall ||
2143
+ didIntersectMesh ||
2144
+ didIntersectGoomba ||
2145
+ Math.abs(
2146
+ this.clampedTempPointToWalkTo.x - this.tempPointToWalkTo2.x
2147
+ ) > 0.001 ||
2148
+ Math.abs(
2149
+ this.clampedTempPointToWalkTo.z - this.tempPointToWalkTo2.z
2150
+ ) > 0.001
2151
+ );
2152
+
2153
+ this.pointToWalkTo.copy(this.tempPointToWalkTo);
2154
+ this.pointToWalkFrom.copy(this.el.object3D.position);
2155
+
2156
+ this.pointToWalkToSphere.object3D.position.copy(this.pointToWalkTo);
2157
+
2158
+ let angle = Math.atan2(
2159
+ this.pointToWalkToOffset.x,
2160
+ this.pointToWalkToOffset.z
2161
+ );
2162
+
2163
+ this.quaternionToTurnFrom.copy(this.el.object3D.quaternion);
2164
+ this.tempEuler.set(0, angle, 0);
2165
+ this.quaternionToTurnTo.setFromEuler(this.tempEuler);
2166
+
2167
+ if (this.slowDown) {
2168
+ this.setStatus("idle");
2169
+ this.slowDown = false;
2170
+ this.isLockedToCamera = true;
2171
+ this.lookAtObject(this.camera);
2172
+ }
2173
+ this.angle = THREE.MathUtils.radToDeg(angle);
2174
+ }
2175
+
2176
+ let interpolation = THREE.MathUtils.inverseLerp(
2177
+ this.lastWalkTick,
2178
+ this.lastWalkTick + this.walkInterval,
2179
+ this.latestTick
2180
+ );
2181
+ this.el.object3D.position.lerpVectors(
2182
+ this.pointToWalkFrom,
2183
+ this.pointToWalkTo,
2184
+ interpolation
2185
+ );
2186
+
2187
+ interpolation = THREE.MathUtils.inverseLerp(
2188
+ this.lastWalkTick,
2189
+ this.lastWalkTick + 200,
2190
+ this.latestTick
2191
+ );
2192
+ if (interpolation <= 1) {
2193
+ this.el.object3D.quaternion.slerpQuaternions(
2194
+ this.quaternionToTurnFrom,
2195
+ this.quaternionToTurnTo,
2196
+ interpolation
2197
+ );
2198
+ }
2199
+ }
2200
+
2201
+ if (
2202
+ this.status == "idle" &&
2203
+ this.floor &&
2204
+ this.landed &&
2205
+ !this.isLookedAt &&
2206
+ time - this.lastTimeLookedAt > this.idleToWalkingTime
2207
+ ) {
2208
+ this.setStatus("walking");
2209
+ }
2210
+
2211
+ if (this.isRolling) {
2212
+ if (this.latestTick - this.startRollingTime < this.rollDuration) {
2213
+ const enitity = this.el;
2214
+ let interpolation = THREE.MathUtils.inverseLerp(
2215
+ this.startRollingTime,
2216
+ this.startRollingTime + this.rollDuration,
2217
+ this.latestTick
2218
+ );
2219
+ interpolation = this.easeInOutElastic(interpolation);
2220
+ enitity.object3D.quaternion.slerpQuaternions(
2221
+ this.rollQuaternionFrom,
2222
+ this.rollQuaternionTo,
2223
+ interpolation
2224
+ );
2225
+ } else {
2226
+ this.isRolling = false;
2227
+ }
2228
+ }
2229
+ if (
2230
+ this.status == "idle" ||
2231
+ this.status == "grabbed" ||
2232
+ this.status == "falling" ||
2233
+ this.status == "walking" ||
2234
+ this.status == "getting up" ||
2235
+ this.status == "petting"
2236
+ ) {
2237
+ if (
2238
+ this.status != "petting" &&
2239
+ time - this.lastBlinkTick > this.blinkInterval
2240
+ ) {
2241
+ this.blink();
2242
+ }
2243
+ if (this.isBlinking) {
2244
+ return;
2245
+ }
2246
+ if (this.isRotatingSquash) {
2247
+ if (
2248
+ this.latestTick - this.squashLookAtStartTime <
2249
+ this.squashLookAtInterval
2250
+ ) {
2251
+ const enitity = this.squash;
2252
+ let interpolation = THREE.MathUtils.inverseLerp(
2253
+ this.squashLookAtStartTime,
2254
+ this.squashLookAtStartTime + this.squashLookAtInterval,
2255
+ this.latestTick
2256
+ );
2257
+ const t = interpolation;
2258
+ interpolation = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
2259
+ enitity.object3D.quaternion.slerpQuaternions(
2260
+ this.squashLookAtQuaternion1,
2261
+ this.squashLookAtQuaternion2,
2262
+ interpolation
2263
+ );
2264
+ } else {
2265
+ this.isRotatingSquash = false;
2266
+ }
2267
+ }
2268
+
2269
+ if (time - this.lastChangeLookAtTick > this.changeLookAtInterval) {
2270
+ this.lastChangeLookAtTick = time;
2271
+ this.checkObjectToLookAt();
2272
+ }
2273
+ if (time - this.lastEyeTick > this.eyeTickInterval) {
2274
+ this.lastEyeTick = time;
2275
+
2276
+ let refocus = false;
2277
+ if (time - this.lastEyeRefocusTick > this.eyeRefocusInterval) {
2278
+ refocus = true;
2279
+ this.lastEyeRefocusTick = time;
2280
+ this.eyeRefocusInterval = THREE.MathUtils.lerp(
2281
+ this.eyeRefocusIntervalRange.min,
2282
+ this.eyeRefocusIntervalRange.max,
2283
+ Math.random()
2284
+ );
2285
+ }
2286
+
2287
+ if (this.objectToLookAt) {
2288
+ if (this.objectToLookAt.components["hand-tracking-controls"]) {
2289
+ this.lookAt(
2290
+ this.objectToLookAt.components["hand-tracking-controls"]
2291
+ .indexTipPosition,
2292
+ refocus
2293
+ );
2294
+ } else {
2295
+ this.lookAt(
2296
+ this.objectToLookAt.object3D.getWorldPosition(
2297
+ this.otherWorldPosition
2298
+ ),
2299
+ refocus
2300
+ );
2301
+ }
2302
+ } else {
2303
+ let changeFocus = false;
2304
+ this.forwardVector
2305
+ .set(0, 0, 1)
2306
+ .applyQuaternion(this.worldQuaternion)
2307
+ .normalize();
2308
+ this.pointToLookAtDirection
2309
+ .subVectors(this.pointToLookAt, this.worldPosition)
2310
+ .normalize();
2311
+ let angle = this.forwardVector.angleTo(this.pointToLookAtDirection);
2312
+ if (false)
2313
+ console.log(
2314
+ `angle from ${this.forwardVector
2315
+ .toArray()
2316
+ .map((value) => value.toFixed(3))
2317
+ .join(",")} to ${this.pointToLookAtDirection
2318
+ .toArray()
2319
+ .map((value) => value.toFixed(3))
2320
+ .join(",")}: ${THREE.MathUtils.radToDeg(angle)}}`
2321
+ );
2322
+ changeFocus =
2323
+ this.hasPointToLookAt && angle > this.pointToLookAtAngleThreshold;
2324
+ if (
2325
+ changeFocus ||
2326
+ time - this.lastWanderEyesTick > this.wanderEyesInterval
2327
+ ) {
2328
+ changeFocus = true;
2329
+ this.lastWanderEyesTick = time;
2330
+ this.wanderEyesInterval = THREE.MathUtils.lerp(
2331
+ this.wanderEyesIntervalRange.min,
2332
+ this.wanderEyesIntervalRange.max,
2333
+ Math.random()
2334
+ );
2335
+
2336
+ this.ray.set(0, 0, 1);
2337
+ const pitch = THREE.MathUtils.lerp(
2338
+ this.wanderEyesEulerRange.pitch.min,
2339
+ this.wanderEyesEulerRange.pitch.max,
2340
+ Math.random()
2341
+ );
2342
+ const yaw = THREE.MathUtils.lerp(
2343
+ this.wanderEyesEulerRange.yaw.min,
2344
+ this.wanderEyesEulerRange.yaw.max,
2345
+ Math.random()
2346
+ );
2347
+ //console.log({ pitch, yaw });
2348
+
2349
+ this.rayEuler.set(
2350
+ THREE.MathUtils.degToRad(pitch),
2351
+ THREE.MathUtils.degToRad(yaw),
2352
+ 0,
2353
+ "YXZ"
2354
+ );
2355
+ this.ray.applyEuler(this.rayEuler);
2356
+ this.ray.applyQuaternion(this.worldQuaternion).normalize();
2357
+ this.raycaster.set(this.worldPosition, this.ray);
2358
+ this.raycaster.far = 10;
2359
+
2360
+ const intersections = this.raycaster.intersectObjects(
2361
+ this.lookAtRaycastTargetObjects,
2362
+ true
2363
+ );
2364
+
2365
+ let hasPointToLookAt = intersections.length > 0;
2366
+ hasPointToLookAt = true; // just set some arbitrary distance
2367
+
2368
+ if (this.hasPointToLookAt != hasPointToLookAt) {
2369
+ this.hasPointToLookAt = hasPointToLookAt;
2370
+ if (!this.hasPointToLookAt) {
2371
+ this.resetEyes();
2372
+ }
2373
+ if (this.hasPointToLookAt && this.showHitSphere) {
2374
+ this.hitSphere.object3D.visible = this.hasPointToLookAt;
2375
+ }
2376
+ }
2377
+
2378
+ if (this.hasPointToLookAt) {
2379
+ const intersection = intersections[0];
2380
+ if (intersection) {
2381
+ //console.log("Hit:", intersection, intersection.point);
2382
+ this.pointToLookAt.copy(intersection.point);
2383
+ } else {
2384
+ this.pointToLookAt
2385
+ .copy(this.ray)
2386
+ .setLength(4)
2387
+ .add(this.worldPosition);
2388
+ }
2389
+ this.hitSphere.object3D.position.copy(this.pointToLookAt);
2390
+ }
2391
+ }
2392
+ if (this.hasPointToLookAt) {
2393
+ this.lookAt(this.pointToLookAt, refocus, this.wanderRefocusScalar);
2394
+ }
2395
+ }
2396
+ }
2397
+ }
2398
+ },
2399
+
2400
+ doesPointIntersectAnyGoombas: function (point) {
2401
+ const index = window.goombas.indexOf(this);
2402
+ return window.goombas
2403
+ .filter((goomba, _index) => _index < index)
2404
+ .filter((goomba) => goomba != this)
2405
+ .some((goomba) => {
2406
+ return goomba.sphere.containsPoint(point);
2407
+ });
2408
+ },
2409
+
2410
+ wallIntersectionThreshold: 0.2,
2411
+ doesDirectionIntersectAnyWalls: function (direction) {
2412
+ this.wallRay = this.wallRay || new THREE.Ray();
2413
+ this.wallRay.set(this.worldPosition, direction);
2414
+ this.wallIntersection = this.wallIntersection || new THREE.Vector3();
2415
+ this.wallDistance = this.wallDistance || new THREE.Vector3();
2416
+ return this.lookAtRaycastTargets
2417
+ .filter(
2418
+ (entity) =>
2419
+ entity.getAttribute("mixin") == "realWorldMeshMixin" ||
2420
+ entity.dataset.worldMesh == "wall"
2421
+ )
2422
+ .some((entity) => {
2423
+ const wallIntersection = entity.components[
2424
+ "my-obb-collider"
2425
+ ].obb.intersectRay(this.wallRay, this.wallIntersection);
2426
+ let intersectsWall = false;
2427
+ if (wallIntersection) {
2428
+ //this.wallIntersectionSphere.object3D.position.copy(wallIntersection);
2429
+ //this.wallIntersectionSphere.object3D.visible = true;
2430
+ const distanceToPoint =
2431
+ this.worldPosition.distanceTo(wallIntersection);
2432
+ intersectsWall = distanceToPoint < this.wallIntersectionThreshold;
2433
+ // console.log({ distanceToPoint });
2434
+ } else {
2435
+ //this.wallIntersectionSphere.object3D.visible = false;
2436
+ }
2437
+ if (intersectsWall) {
2438
+ // console.log(
2439
+ // "intersects wall",
2440
+ // entity.getAttribute("data-world-mesh")
2441
+ // );
2442
+ }
2443
+ return intersectsWall;
2444
+ });
2445
+ },
2446
+
2447
+ doesPointIntersectAnyMeshes: function (point) {
2448
+ return this.lookAtRaycastTargets
2449
+ .filter((entity) => entity.getAttribute("mixin") == "realWorldMeshMixin")
2450
+ .filter((entity) => entity != this.floor)
2451
+ .some((entity) => {
2452
+ const containsPoint =
2453
+ entity.components["my-obb-collider"].obb.containsPoint(point);
2454
+ if (containsPoint) {
2455
+ // console.log(
2456
+ // "congtains point with",
2457
+ // entity.getAttribute("data-world-mesh")
2458
+ // );
2459
+ }
2460
+ return containsPoint;
2461
+ });
2462
+ },
2463
+
2464
+ orientations: [
2465
+ "upright",
2466
+ "leftSide",
2467
+ "rightSide",
2468
+ "frontSide",
2469
+ "backSide",
2470
+ "upsideDown",
2471
+ "unknown",
2472
+ ],
2473
+ worldBasis: {
2474
+ forward: new THREE.Vector3(0, 0, 1),
2475
+ up: new THREE.Vector3(0, 1, 0),
2476
+ right: new THREE.Vector3(1, 0, 0),
2477
+ },
2478
+ orientationAngleThreshold: THREE.MathUtils.degToRad(10),
2479
+ checkOrientation: function () {
2480
+ if (!this.orientationVectors) {
2481
+ this.orientationVectors = {
2482
+ forward: new THREE.Vector3(),
2483
+ up: new THREE.Vector3(),
2484
+ right: new THREE.Vector3(),
2485
+ };
2486
+ }
2487
+ const { forward, up, right } = this.orientationVectors;
2488
+ // this.el.object3D.getWorldQuaternion(this.worldQuaternion);
2489
+ forward.set(0, 0, 1).applyQuaternion(this.worldQuaternion).normalize();
2490
+ up.set(0, 1, 0).applyQuaternion(this.worldQuaternion).normalize();
2491
+ right.set(1, 0, 0).applyQuaternion(this.worldQuaternion).normalize();
2492
+
2493
+ const upAngle = this.worldBasis.up.angleTo(up);
2494
+ const rightAngle = this.worldBasis.up.angleTo(right);
2495
+ const forwardAngle = this.worldBasis.up.angleTo(forward);
2496
+
2497
+ let newOrientation = "unknown";
2498
+ if (Math.abs(forwardAngle - Math.PI) < this.orientationAngleThreshold) {
2499
+ newOrientation = "frontSide";
2500
+ } else if (forwardAngle < this.orientationAngleThreshold) {
2501
+ newOrientation = "backSide";
2502
+ } else if (
2503
+ Math.abs(rightAngle - Math.PI) < this.orientationAngleThreshold
2504
+ ) {
2505
+ newOrientation = "leftSide";
2506
+ } else if (rightAngle < this.orientationAngleThreshold) {
2507
+ newOrientation = "rightSide";
2508
+ } else if (Math.abs(upAngle - Math.PI) < this.orientationAngleThreshold) {
2509
+ newOrientation = "upsideDown";
2510
+ } else if (upAngle < this.orientationAngleThreshold) {
2511
+ newOrientation = "upright";
2512
+ }
2513
+
2514
+ this.setOrientation(newOrientation);
2515
+ },
2516
+ setOrientation: function (newOrientation) {
2517
+ if (!this.orientations.includes(newOrientation)) {
2518
+ console.error(`invalid orientation ${newOrientation}`);
2519
+ return;
2520
+ }
2521
+ if (newOrientation == this.orientation) {
2522
+ return;
2523
+ }
2524
+ this.orientation = newOrientation;
2525
+ // console.log(`updated orientation to "${this.orientation}"`);
2526
+ },
2527
+ rollDistance: 0.15,
2528
+ easeInOutElastic: function (x) {
2529
+ const c5 = (2 * Math.PI) / 4.5;
2530
+
2531
+ return x === 0
2532
+ ? 0
2533
+ : x === 1
2534
+ ? 1
2535
+ : x < 0.5
2536
+ ? -(Math.pow(2, 20 * x - 10) * Math.sin((20 * x - 11.125) * c5)) / 2
2537
+ : (Math.pow(2, -20 * x + 10) * Math.sin((20 * x - 11.125) * c5)) / 2 + 1;
2538
+ },
2539
+ getUp: async function (dur = 1500, easing = "easeInOutElastic") {
2540
+ // this.el.object3D.getWorldQuaternion(this.worldQuaternion);
2541
+
2542
+ this.checkOrientation();
2543
+
2544
+ if (this.orientation == "upright") {
2545
+ return;
2546
+ }
2547
+
2548
+ setTimeout(() => {
2549
+ this.playGetUpSound();
2550
+ }, dur * 0.36);
2551
+
2552
+ this.el.removeAttribute("grabbable");
2553
+ setTimeout(() => {
2554
+ this.el.setAttribute("grabbable", "");
2555
+ }, dur);
2556
+
2557
+ this.getUpEuler = this.getUpEuler || new THREE.Euler();
2558
+ // let reorder = "XYZ";
2559
+ // switch (this.orientation) {
2560
+ // case "rightSide":
2561
+ // case "leftSide":
2562
+ // reorder = "XYZ";
2563
+ // break;
2564
+ // case "frontSide":
2565
+ // case "backSide":
2566
+ // reorder = "YZX";
2567
+ // break;
2568
+ // case "upsideDown":
2569
+ // reorder = "XYZ";
2570
+ // break;
2571
+ // }
2572
+ // this.getUpEuler.copy(this.el.object3D.rotation).reorder(reorder);
2573
+ // let yaw = THREE.MathUtils.radToDeg(this.getUpEuler.y);
2574
+
2575
+ let yaw = this.getEntityYaw();
2576
+
2577
+ let from = [0, yaw, 0];
2578
+ switch (this.orientation) {
2579
+ case "rightSide":
2580
+ from[2] = 90;
2581
+ break;
2582
+ case "leftSide":
2583
+ from[2] = -90;
2584
+ break;
2585
+ case "upsideDown":
2586
+ from[0] = 180;
2587
+ //from[1] *= -1;
2588
+ //yaw *= -1;
2589
+ break;
2590
+ case "frontSide":
2591
+ from[0] = 90;
2592
+ break;
2593
+ case "backSide":
2594
+ from[0] = -90;
2595
+ break;
2596
+ default:
2597
+ break;
2598
+ }
2599
+ const to = [0, yaw, 0];
2600
+ from = from;
2601
+
2602
+ if (true) {
2603
+ if (true) {
2604
+ this.rollTempEuler.set(
2605
+ ...from.map((value) => THREE.MathUtils.degToRad(value)),
2606
+ "YXZ"
2607
+ );
2608
+ // console.log(this.rollTempEuler);
2609
+ this.rollQuaternionFrom.setFromEuler(this.rollTempEuler);
2610
+ } else {
2611
+ this.rollQuaternionFrom.copy(this.el.object3D.quaternion);
2612
+ }
2613
+
2614
+ this.rollTempEuler.set(
2615
+ ...to.map((value) => THREE.MathUtils.degToRad(value))
2616
+ );
2617
+
2618
+ this.rollQuaternionTo.setFromEuler(this.rollTempEuler);
2619
+
2620
+ this.isRolling = true;
2621
+ this.startRollingTime = this.latestTick;
2622
+ this.rollDuration = dur;
2623
+ } else {
2624
+ const components = ["x", "y", "z"];
2625
+ components.forEach((component, index) => {
2626
+ const attribute = `animation__rollUpright${component}`;
2627
+ this.el.removeAttribute(attribute);
2628
+ if (to[index] == from[index]) {
2629
+ console.log("direct assignment", component, to[index]);
2630
+ //this.el.object3D.rotation[component] = to[index];
2631
+ } else {
2632
+ console.log("interpolating", component, from[index], to[index]);
2633
+ this.el.setAttribute(attribute, {
2634
+ property: `object3D.rotation.${component}`,
2635
+ to: to[index],
2636
+ from: from[index],
2637
+ dur,
2638
+ easing,
2639
+ });
2640
+ }
2641
+ });
2642
+ }
2643
+
2644
+ let rollScalar = 1;
2645
+ switch (this.orientation) {
2646
+ case "frontSide":
2647
+ rollScalar = -1;
2648
+ break;
2649
+ case "leftSide":
2650
+ rollScalar = -1;
2651
+ break;
2652
+ case "upsideDown":
2653
+ rollScalar = -1;
2654
+ break;
2655
+ }
2656
+
2657
+ this.getUpPosition = this.getUpPosition || new THREE.Vector3();
2658
+ switch (this.orientation) {
2659
+ case "frontSide":
2660
+ case "backSide":
2661
+ this.getUpPosition.set(0, 0, rollScalar * this.rollDistance);
2662
+ break;
2663
+ case "rightSide":
2664
+ case "leftSide":
2665
+ this.getUpPosition.set(rollScalar * this.rollDistance, 0, 0);
2666
+ break;
2667
+ case "upsideDown":
2668
+ this.getUpPosition.set(0, 0, rollScalar * this.rollDistance);
2669
+ break;
2670
+ }
2671
+ this.getUpEuler.set(0, THREE.MathUtils.degToRad(yaw), 0);
2672
+ this.getUpPosition.applyEuler(this.getUpEuler);
2673
+ this.getUpPosition.add(this.worldPosition);
2674
+ this.el.setAttribute("animation__moveUpright", {
2675
+ property: "position",
2676
+ to: this.getUpPosition.toArray().join(" "),
2677
+ from: this.worldPosition
2678
+ .toArray()
2679
+ .map((value) => (Math.abs(value) < 0.001 ? 0 : value))
2680
+ .join(" "),
2681
+ dur,
2682
+ easing,
2683
+ });
2684
+
2685
+ let dominantSide = "right";
2686
+ switch (this.orientation) {
2687
+ case "leftSide":
2688
+ dominantSide = "right";
2689
+ break;
2690
+ case "rightSide":
2691
+ dominantSide = "left";
2692
+ break;
2693
+ default:
2694
+ dominantSide = Math.round(Math.random()) ? "left" : "right";
2695
+ break;
2696
+ }
2697
+ this.sides.forEach((side) => {
2698
+ let offsetDur = 1;
2699
+ switch (this.orientation) {
2700
+ case "leftSide":
2701
+ case "rightSide":
2702
+ offsetDur = 1;
2703
+ break;
2704
+ default:
2705
+ offsetDur = side != dominantSide ? 1.0 : 1.1;
2706
+ break;
2707
+ }
2708
+
2709
+ setTimeout(async () => {
2710
+ let pitch = 0.5;
2711
+ let roll = 0.5;
2712
+ switch (this.orientation) {
2713
+ case "frontSide":
2714
+ pitch = 0;
2715
+ break;
2716
+ case "rightSide":
2717
+ roll = 0.2;
2718
+ break;
2719
+ case "leftSide":
2720
+ roll = 0.8;
2721
+ break;
2722
+ default:
2723
+ pitch = 1;
2724
+ break;
2725
+ }
2726
+ await this.setLegRotation(
2727
+ side,
2728
+ { pitch, roll },
2729
+ dur * 0.1,
2730
+ "easeInOutQuad"
2731
+ );
2732
+ pitch = 0.5;
2733
+ roll = 0.5;
2734
+ switch (this.orientation) {
2735
+ case "frontSide":
2736
+ pitch = 0.7;
2737
+ break;
2738
+ case "rightSide":
2739
+ roll = 0.8;
2740
+ break;
2741
+ case "leftSide":
2742
+ roll = 0.2;
2743
+ break;
2744
+ default:
2745
+ pitch = 0.3;
2746
+ break;
2747
+ }
2748
+ await this.setLegRotation(
2749
+ side,
2750
+ { pitch, roll },
2751
+ dur * 0.1,
2752
+ "easeInOutQuad"
2753
+ );
2754
+ await this.setLegRotation(
2755
+ side,
2756
+ { pitch: 0.5, roll: 0.5 },
2757
+ dur * 0.1,
2758
+ "easeInOutQuad"
2759
+ );
2760
+ }, dur * 0.3 * offsetDur);
2761
+ });
2762
+
2763
+ return new Promise((resolve) => {
2764
+ setTimeout(() => {
2765
+ resolve();
2766
+ }, dur);
2767
+ });
2768
+ },
2769
+
2770
+ getEntityYaw: function () {
2771
+ const forward = new THREE.Vector3(0, 0, -1); // Local forward
2772
+ switch (this.orientation) {
2773
+ case "upright":
2774
+ forward.set(0, 0, -1);
2775
+ break;
2776
+ case "leftSide":
2777
+ forward.set(0, 0, 1);
2778
+ break;
2779
+ case "rightSide":
2780
+ forward.set(0, 0, 1);
2781
+ break;
2782
+ case "upsideDown":
2783
+ forward.set(0, 0, -1);
2784
+ break;
2785
+ case "backSide":
2786
+ forward.set(0, -1, 0);
2787
+ break;
2788
+ case "frontSide":
2789
+ forward.set(0, 1, 0);
2790
+ break;
2791
+ }
2792
+ forward.applyQuaternion(this.worldQuaternion); // Transform it into world space
2793
+
2794
+ forward.y = 0; // Flatten onto XZ plane
2795
+ forward.normalize();
2796
+
2797
+ let yaw = Math.atan2(forward.x, forward.z); // X first, then Z
2798
+ yaw = THREE.MathUtils.radToDeg(yaw); // In radians
2799
+ return yaw;
2800
+ },
2801
+
2802
+ walkingStepScalar: 0.5,
2803
+ startWalking: async function (
2804
+ dur = 320,
2805
+ pitch = 0.7,
2806
+ easing = "easeInOutQuad"
2807
+ ) {
2808
+ const legPromise = new Promise(async (resolve) => {
2809
+ if (this.status != "walking") {
2810
+ return;
2811
+ }
2812
+ await this.setLegsRotation({ pitch }, dur, easing, false, "normal", true);
2813
+ if (this.status != "walking") {
2814
+ return;
2815
+ }
2816
+ await this.setLegsRotation(
2817
+ { pitch: 1 - pitch, pitch2: pitch },
2818
+ dur,
2819
+ easing,
2820
+ true,
2821
+ "alternate",
2822
+ true
2823
+ );
2824
+ resolve();
2825
+ });
2826
+ const footPromise = new Promise((resolve) => {
2827
+ const footEasing = "easeInCubic";
2828
+ const footPitches = [0.2, 0.7];
2829
+ const footRolls = [0.4, 0.6];
2830
+ const footDur = dur;
2831
+ setTimeout(async () => {
2832
+ const promises = this.sides.map(async (side) => {
2833
+ let footPitch, footPitch2;
2834
+ if (side == "right") {
2835
+ footPitch = footPitches[0];
2836
+ footPitch2 = footPitches[1];
2837
+ } else {
2838
+ footPitch = footPitches[0];
2839
+ footPitch2 = footPitches[1];
2840
+ }
2841
+
2842
+ let footRoll, footRoll2;
2843
+ if (side == "right") {
2844
+ footRoll = footRolls[1];
2845
+ footRoll2 = footRolls[0];
2846
+ } else {
2847
+ footRoll = footRolls[0];
2848
+ footRoll2 = footRolls[1];
2849
+ }
2850
+ if (side == "left") {
2851
+ if (this.status != "walking") {
2852
+ return;
2853
+ }
2854
+ await this.setLegRotation(
2855
+ side,
2856
+ { pitch: footPitch, roll: footRoll },
2857
+ footDur,
2858
+ footEasing,
2859
+ false,
2860
+ "normal",
2861
+ false,
2862
+ true
2863
+ );
2864
+ }
2865
+ if (this.status != "walking") {
2866
+ return;
2867
+ }
2868
+ await this.setLegRotation(
2869
+ side,
2870
+ {
2871
+ pitch: footPitch,
2872
+ pitch2: footPitch2,
2873
+ roll: footRoll,
2874
+ roll2: footRoll2,
2875
+ },
2876
+ footDur,
2877
+ footEasing,
2878
+ true,
2879
+ "alternate",
2880
+ false,
2881
+ true
2882
+ );
2883
+ });
2884
+ await Promise.all(promises);
2885
+ resolve();
2886
+ }, dur * 0.2);
2887
+ });
2888
+
2889
+ this.squash.removeAttribute("animation__rot");
2890
+ this.squash.setAttribute("animation__rot", {
2891
+ property: "rotation",
2892
+ to: "0 0 1",
2893
+ from: "0 0 -1",
2894
+ dur: dur,
2895
+ easing,
2896
+ loop: true,
2897
+ dir: "alternate",
2898
+ });
2899
+
2900
+ this.squash.removeAttribute("animation__scale");
2901
+ this.squash.setAttribute("animation__scale", {
2902
+ property: "scale",
2903
+ to: "1 0.98 1",
2904
+ from: "1 1 1",
2905
+ dur: dur / 2,
2906
+ easing: "easeInOutQuad",
2907
+ loop: true,
2908
+ dir: "alternate",
2909
+ });
2910
+
2911
+ await Promise.all([legPromise, footPromise]);
2912
+ },
2913
+
2914
+ stopWalking: async function () {
2915
+ this.squash.removeAttribute("animation__scale");
2916
+ this.squash.removeAttribute("animation__rot");
2917
+ this.resetLegs();
2918
+ },
2919
+
2920
+ statuses: ["idle", "grabbed", "falling", "getting up", "walking", "petting"],
2921
+
2922
+ resetSquashRotation: function () {
2923
+ this.squash.removeAttribute("animation__rot");
2924
+ this.squash.setAttribute("animation__rot", {
2925
+ property: "rotation",
2926
+ to: `0 0 0`,
2927
+ dur: 300,
2928
+ easing: "easeOutQuad",
2929
+ });
2930
+ },
2931
+
2932
+ setStatus: async function (newStatus) {
2933
+ if (this.status == newStatus) {
2934
+ return;
2935
+ }
2936
+ if (!this.statuses.includes(newStatus)) {
2937
+ console.error(`invalid status "${newStatus}"`);
2938
+ return;
2939
+ }
2940
+ this.isLockedToCamera = false;
2941
+ if (this.status == "walking") {
2942
+ this.stopWalking();
2943
+ }
2944
+ if (this.status == "grabbed") {
2945
+ this.playReleaseSound();
2946
+ }
2947
+ if (false && this.status == "getting up") {
2948
+ //this.returnGetUpSound();
2949
+ }
2950
+ if (this.status == "petting") {
2951
+ this.el.sceneEl.emit("stopPetting", { side: this.petSide });
2952
+ if (this.purrSound) {
2953
+ this.stopPurrSound();
2954
+ this.playPurrFadeOutSound();
2955
+ }
2956
+ }
2957
+
2958
+ if (this.status == "petting") {
2959
+ this.blink();
2960
+ setTimeout(() => {
2961
+ this.setEmotion("default");
2962
+ }, 200);
2963
+ }
2964
+
2965
+ this.status = newStatus;
2966
+ // console.log(`new status "${this.status}"`);
2967
+
2968
+ if (this.status == "petting") {
2969
+ this.el.sceneEl.emit("startPetting", { side: this.petSide });
2970
+ this.playPurrSound();
2971
+ }
2972
+
2973
+ if (this.status == "petting") {
2974
+ this.setEmotion("happy");
2975
+ }
2976
+
2977
+ if (this.status == "grabbed") {
2978
+ this.playGrabSound();
2979
+ }
2980
+
2981
+ this.el.removeAttribute("animation__turn");
2982
+
2983
+ if (this.squash.hasAttribute("animation__rot")) {
2984
+ this.squash.removeAttribute("animation__rot");
2985
+ this.squash.setAttribute("animation__rot", {
2986
+ property: "rotation",
2987
+ to: `0 0 0`,
2988
+ dur: 200,
2989
+ easing: "easeOutQuad",
2990
+ });
2991
+ }
2992
+ if (this.squash.hasAttribute("animation__scale")) {
2993
+ this.squash.removeAttribute("animation__scale");
2994
+ this.squash.setAttribute("animation__scale", {
2995
+ property: "scale",
2996
+ to: `1 1 1`,
2997
+ dur: 200,
2998
+ easing: "easeOutQuad",
2999
+ });
3000
+ }
3001
+
3002
+ this.clearEyesRotationAnimation();
3003
+ this.clearEyesRollAnimation();
3004
+ this.clearEyesScaleAnimation();
3005
+ this.clearLegsRotationAnimation();
3006
+ this.clearLegsRotationAnimation(true);
3007
+
3008
+ this.resetEyesScale();
3009
+
3010
+ this.resetLegs();
3011
+
3012
+ this.resetSquashRotation();
3013
+
3014
+ this.pointToWalkToSphere.setAttribute("visible", false);
3015
+
3016
+ if (this.punchable) {
3017
+ if (this.ignorePunchStatuses.includes(this.status)) {
3018
+ this.el.classList.remove("punchable");
3019
+ } else {
3020
+ this.el.classList.add("punchable");
3021
+ }
3022
+ }
3023
+
3024
+ switch (this.status) {
3025
+ case "grabbed":
3026
+ //this.setScale(1, 500);
3027
+ const easing = "easeInOutQuad";
3028
+ const dur1 = 900;
3029
+ const durDelay = 0.5;
3030
+ let rightDominant = Math.round(Math.random());
3031
+ rightDominant = true;
3032
+ this.setLegRotation(
3033
+ !rightDominant ? "right" : "left",
3034
+ { pitch: 0.5, pitch2: 0.7 },
3035
+ dur1,
3036
+ easing,
3037
+ true,
3038
+ "alternate",
3039
+ true
3040
+ );
3041
+ setTimeout(async () => {
3042
+ if (this.status != "grabbed") {
3043
+ return;
3044
+ }
3045
+ await this.setLegRotation(
3046
+ !rightDominant ? "right" : "left",
3047
+ { pitch: 0.4 },
3048
+ dur1,
3049
+ easing,
3050
+ 0,
3051
+ "normal",
3052
+ true,
3053
+ true
3054
+ );
3055
+ if (this.status != "grabbed") {
3056
+ return;
3057
+ }
3058
+ await this.setLegRotation(
3059
+ !rightDominant ? "right" : "left",
3060
+ { pitch: 0.5, pitch2: 0.4 },
3061
+ dur1,
3062
+ easing,
3063
+ true,
3064
+ "alternate",
3065
+ true,
3066
+ true
3067
+ );
3068
+ }, dur1 * durDelay);
3069
+
3070
+ const dur2 = 920;
3071
+ this.setLegRotation(
3072
+ rightDominant ? "right" : "left",
3073
+ { pitch: 0.3, pitch2: 0.8 },
3074
+ dur2,
3075
+ easing,
3076
+ true,
3077
+ "alternate",
3078
+ true
3079
+ );
3080
+ setTimeout(async () => {
3081
+ if (this.status != "grabbed") {
3082
+ return;
3083
+ }
3084
+ await this.setLegRotation(
3085
+ rightDominant ? "right" : "left",
3086
+ { pitch: 0.4 },
3087
+ dur2,
3088
+ easing,
3089
+ 0,
3090
+ "normal",
3091
+ true,
3092
+ true
3093
+ );
3094
+ if (this.status != "grabbed") {
3095
+ return;
3096
+ }
3097
+ await this.setLegRotation(
3098
+ rightDominant ? "right" : "left",
3099
+ { pitch: 0.5, pitch2: 0.4 },
3100
+ dur2,
3101
+ easing,
3102
+ true,
3103
+ "alternate",
3104
+ true,
3105
+ true
3106
+ );
3107
+ }, dur2 * durDelay);
3108
+ this.setFloor();
3109
+ break;
3110
+ case "getting up":
3111
+ await this.getUp();
3112
+ this.lastChangeLookAtTick = 0;
3113
+ this.lastEyeRefocusTick = 0;
3114
+ this.setStatus("walking");
3115
+ break;
3116
+ case "idle":
3117
+ break;
3118
+ case "falling":
3119
+ this.setLegsRotation(
3120
+ { pitch: 0, pitch2: 1 },
3121
+ 200,
3122
+ "easeInOutQuad",
3123
+ true,
3124
+ "alternate",
3125
+ true
3126
+ );
3127
+ //this.setScale(1.5, 1000);
3128
+ break;
3129
+ case "walking":
3130
+ // this.setPhysicsEnabled(true);
3131
+ //this.pointToWalkToSphere.setAttribute("visible", true);
3132
+ this.startWalking();
3133
+ break;
3134
+ default:
3135
+ break;
3136
+ }
3137
+ },
3138
+
3139
+ setScale: function (scale, dur, easing = "easeOutQuad") {
3140
+ this.scale = scale;
3141
+ const entity = this.el;
3142
+ if (dur == 0) {
3143
+ entity.object3D.scale.set(scale, scale, scale);
3144
+ } else {
3145
+ entity.setAttribute("animation__scale", {
3146
+ property: "scale",
3147
+ to: `${scale} ${scale} ${scale}`,
3148
+ from: "1 1 1",
3149
+ dur,
3150
+ easing,
3151
+ });
3152
+ }
3153
+ },
3154
+
3155
+ waitForSoundToLoad: async function (entity) {
3156
+ return new Promise((resolve) => {
3157
+ if (entity.components.sound) {
3158
+ resolve();
3159
+ } else {
3160
+ let onComponentInitialized = (event) => {
3161
+ if (event.detail.name == "sound") {
3162
+ entity.removeEventListener(
3163
+ "componentinitialized",
3164
+ onComponentInitialized
3165
+ );
3166
+ resolve();
3167
+ }
3168
+ };
3169
+ onComponentInitialized = onComponentInitialized.bind(this);
3170
+
3171
+ entity.addEventListener("componentinitialized", onComponentInitialized);
3172
+ }
3173
+ });
3174
+ },
3175
+
3176
+ playGetUpSound: function () {
3177
+ this.playSound("getUpSound");
3178
+ },
3179
+
3180
+ playPurrSound: function () {
3181
+ this.playSound("purrSound");
3182
+ },
3183
+ stopPurrSound: function () {
3184
+ this.returnSound("purrSound");
3185
+ },
3186
+
3187
+ playPurrFadeOutSound: function () {
3188
+ this.playSound("purrSoundFadeOut");
3189
+ },
3190
+
3191
+ playGrabSound: function () {
3192
+ this.returnSound("releaseSound");
3193
+ this.playSound("grabSound");
3194
+ },
3195
+ playReleaseSound: function () {
3196
+ this.returnSound("grabSound");
3197
+ this.playSound("releaseSound");
3198
+ },
3199
+
3200
+ playSound: async function (name) {
3201
+ this.returnSound(name);
3202
+ const poolName = `pool__${name.toLowerCase()}`;
3203
+ const sound = (this[name] =
3204
+ this.el.sceneEl.components[poolName].requestEntity());
3205
+ sound.poolName = poolName;
3206
+ this.el.object3D.getWorldPosition(sound.object3D.position);
3207
+ sound.play();
3208
+ await this.waitForSoundToLoad(sound);
3209
+ sound.components.sound.playSound();
3210
+ sound.addEventListener("sound-ended", () => this.returnSound(name), {
3211
+ once: true,
3212
+ });
3213
+ },
3214
+ returnSound: function (name) {
3215
+ const sound = this[name];
3216
+ if (sound) {
3217
+ sound.components["sound"].stopSound();
3218
+ this.el.sceneEl.components[sound.poolName].returnEntity(sound);
3219
+ this[name] = undefined;
3220
+ }
3221
+ },
3222
+
3223
+ playShellHitSound: function () {
3224
+ this.playSound("shellHitSound");
3225
+ },
3226
+
3227
+ playPunchSound: function () {
3228
+ this.playSound("punchSound");
3229
+ },
3230
+ playPunchSqueakSound: function () {
3231
+ this.playSound("punchSqueakSound");
3232
+ },
3233
+
3234
+ updateLookAtTargets() {
3235
+ this.lookAtTargets = Array.from(
3236
+ this.el.sceneEl.querySelectorAll(this.data.lookAt)
3237
+ );
3238
+ },
3239
+ updateLookAtRaycastTargets() {
3240
+ this.lookAtRaycastTargets = Array.from(
3241
+ this.el.sceneEl.querySelectorAll(this.data.lookAtRaycast)
3242
+ );
3243
+ this.lookAtRaycastTargetObjects = this.lookAtRaycastTargets.map(
3244
+ (entity) => entity.object3D
3245
+ );
3246
+ if (this.lookAtRaycastTargetObjects.length > 0) {
3247
+ clearInterval(this.lookAtRaycastSelectorInterval);
3248
+ }
3249
+ },
3250
+ });