|
@@ -14,7 +14,7 @@ import { GameEvent } from '../core/events.js';
|
14
|
14
|
const BALL_RADIUS = 0.275;
|
15
|
15
|
|
16
|
16
|
/** Minimum distance for scoring 3 points */
|
17
|
|
-const THREE_POINT_THRESHOLD = 6.0;
|
|
17
|
+const THREE_POINT_THRESHOLD = 5.0;
|
18
|
18
|
|
19
|
19
|
/** Shoot angle */
|
20
|
20
|
const SHOOT_ANGLE = Math.PI / 4;
|
|
@@ -37,6 +37,15 @@ const MAX_DISTANCE = 15;
|
37
|
37
|
/** Maximum distance in the y-axis from the camera to the ball, so that the ball is considered "lost" in the thrown state */
|
38
|
38
|
const MAX_Y_DISTANCE = 5;
|
39
|
39
|
|
|
40
|
+/** Frequency of the shining effect, in Hz */
|
|
41
|
+const SHINE_FREQUENCY = 2.0;
|
|
42
|
+
|
|
43
|
+/** Maximum brightness of the ball when the shining effect is applied (a value in [0,1]) */
|
|
44
|
+const SHINE_MAX_BRIGHTNESS = 0.25;
|
|
45
|
+
|
|
46
|
+/** Duration of fading of the shining effect, in seconds */
|
|
47
|
+const SHINE_FADE_DURATION = 0.5;
|
|
48
|
+
|
40
|
49
|
/** Collision flag for the backboard */
|
41
|
50
|
const FLAG_BACKBOARD = 1;
|
42
|
51
|
|
|
@@ -66,10 +75,13 @@ export class Ball extends Entity
|
66
|
75
|
this._plane = new BABYLON.Plane(0, 0, 1, 0);
|
67
|
76
|
this._planeOrigin = new BABYLON.Vector3();
|
68
|
77
|
this._positionWhenThrown = new BABYLON.Vector3();
|
|
78
|
+ this._hoopPosition = new BABYLON.Vector3();
|
69
|
79
|
this._mesh = null;
|
70
|
80
|
this._lastTrigger = '';
|
71
|
81
|
this._collisionFlags = 0;
|
72
|
82
|
this._locked = false;
|
|
83
|
+ this._shineFactor = 0;
|
|
84
|
+ this._shineTimer = 0;
|
73
|
85
|
}
|
74
|
86
|
|
75
|
87
|
/**
|
|
@@ -103,6 +115,7 @@ export class Ball extends Entity
|
103
|
115
|
}
|
104
|
116
|
|
105
|
117
|
this._updatePlane();
|
|
118
|
+ this._shine();
|
106
|
119
|
|
107
|
120
|
const fn = this._runState[this._state];
|
108
|
121
|
fn.call(this);
|
|
@@ -211,7 +224,7 @@ export class Ball extends Entity
|
211
|
224
|
if(pointer.movementDuration == 0)
|
212
|
225
|
return pointer.velocity.times(0);
|
213
|
226
|
|
214
|
|
- const averageSpeed = pointer.movementLength / pointer.movementDuration;
|
|
227
|
+ const averageSpeed = pointer.movementLength / pointer.duration;
|
215
|
228
|
const direction = pointer.initialPosition.directionTo(pointer.position);
|
216
|
229
|
|
217
|
230
|
return direction.times(averageSpeed);
|
|
@@ -274,16 +287,9 @@ export class Ball extends Entity
|
274
|
287
|
*/
|
275
|
288
|
_score()
|
276
|
289
|
{
|
277
|
|
- const pointA = this._mesh.absolutePosition;
|
278
|
|
- const pointB = this._positionWhenThrown;
|
279
|
|
-
|
280
|
|
- const plane = BABYLON.Plane.FromPositionAndNormal(this.ar.root.absolutePosition, this.ar.root.up);
|
281
|
|
- const projectedPointA = this._orthogonalProjection(plane, pointA);
|
282
|
|
- const projectedPointB = this._orthogonalProjection(plane, pointB);
|
283
|
|
-
|
284
|
|
- const distance = BABYLON.Vector3.Distance(projectedPointA, projectedPointB);
|
|
290
|
+ const distance = this._calculateDistanceToHoop(this._positionWhenThrown);
|
285
|
291
|
const score = this._calculateScore(distance);
|
286
|
|
- const position = pointA;
|
|
292
|
+ const position = this._mesh.absolutePosition;
|
287
|
293
|
|
288
|
294
|
this._broadcast(new GameEvent('scored', { score, position }));
|
289
|
295
|
}
|
|
@@ -299,6 +305,24 @@ export class Ball extends Entity
|
299
|
305
|
}
|
300
|
306
|
|
301
|
307
|
/**
|
|
308
|
+ * Calculate the distance between a point and the hoop
|
|
309
|
+ * @param {BABYLON.Vector3} absolutePosition
|
|
310
|
+ * @returns {number}
|
|
311
|
+ */
|
|
312
|
+ _calculateDistanceToHoop(absolutePosition)
|
|
313
|
+ {
|
|
314
|
+ const ar = this.ar;
|
|
315
|
+ const pointA = absolutePosition;
|
|
316
|
+ const pointB = ar.root.absolutePosition.add(this._hoopPosition);
|
|
317
|
+
|
|
318
|
+ const groundLike = BABYLON.Plane.FromPositionAndNormal(ar.root.absolutePosition, ar.root.up);
|
|
319
|
+ const projectedPointA = this._orthogonalProjection(groundLike, pointA);
|
|
320
|
+ const projectedPointB = this._orthogonalProjection(groundLike, pointB);
|
|
321
|
+
|
|
322
|
+ return BABYLON.Vector3.Distance(projectedPointA, projectedPointB);
|
|
323
|
+ }
|
|
324
|
+
|
|
325
|
+ /**
|
302
|
326
|
* Put this._plane in front of the camera
|
303
|
327
|
* @returns {void}
|
304
|
328
|
*/
|
|
@@ -366,6 +390,51 @@ export class Ball extends Entity
|
366
|
390
|
}
|
367
|
391
|
|
368
|
392
|
/**
|
|
393
|
+ * Make the ball shine as a visual cue to indicate long distances
|
|
394
|
+ * @returns {void}
|
|
395
|
+ */
|
|
396
|
+ _shine()
|
|
397
|
+ {
|
|
398
|
+ const b = this._shineBrightness();
|
|
399
|
+
|
|
400
|
+ this._mesh.getChildMeshes().forEach(child => {
|
|
401
|
+ if(child.material)
|
|
402
|
+ child.material.emissiveColor.set(b, b, b);
|
|
403
|
+ });
|
|
404
|
+ }
|
|
405
|
+
|
|
406
|
+ /**
|
|
407
|
+ * Compute the brightness of the shining ball
|
|
408
|
+ * @returns {number} in [0,1]
|
|
409
|
+ */
|
|
410
|
+ _shineBrightness()
|
|
411
|
+ {
|
|
412
|
+ if(this._state == 'thrown' || !this._mesh.isEnabled) {
|
|
413
|
+ this._shineFactor = this._shineTimer = 0;
|
|
414
|
+ return 0;
|
|
415
|
+ }
|
|
416
|
+
|
|
417
|
+ const time = this.ar.session.time;
|
|
418
|
+ const dt = time.delta;
|
|
419
|
+ const rate = 1.0 / SHINE_FADE_DURATION;
|
|
420
|
+ const distance = this._calculateDistanceToHoop(this._mesh.absolutePosition);
|
|
421
|
+
|
|
422
|
+ if(distance >= THREE_POINT_THRESHOLD) {
|
|
423
|
+ this._shineFactor = 1;
|
|
424
|
+ this._shineTimer += dt;
|
|
425
|
+ }
|
|
426
|
+ else {
|
|
427
|
+ this._shineFactor = Math.max(0, this._shineFactor - rate * dt);
|
|
428
|
+ if(this._shineFactor == 0)
|
|
429
|
+ this._shineTimer = 0;
|
|
430
|
+ }
|
|
431
|
+
|
|
432
|
+ const t = this._shineTimer;
|
|
433
|
+ const s = 0.5 - 0.5 * Math.cos(2 * Math.PI * SHINE_FREQUENCY * t);
|
|
434
|
+ return this._shineFactor * SHINE_MAX_BRIGHTNESS * s;
|
|
435
|
+ }
|
|
436
|
+
|
|
437
|
+ /**
|
369
|
438
|
* Create a root node with a physics impostor
|
370
|
439
|
* @param {BABYLON.Mesh} mesh from gltf
|
371
|
440
|
* @param {number} radius radius of the ball
|
|
@@ -374,9 +443,10 @@ export class Ball extends Entity
|
374
|
443
|
_createPhysicsRoot(mesh)
|
375
|
444
|
{
|
376
|
445
|
const r = BALL_RADIUS;
|
|
446
|
+ const r_ = r - 0.003;
|
377
|
447
|
|
378
|
448
|
// prepare the mesh
|
379
|
|
- mesh.scaling.set(r, r, r); // original radius = 1
|
|
449
|
+ mesh.scaling.set(r_, r_, r_); // original radius = 1
|
380
|
450
|
mesh.getChildMeshes().forEach(child => {
|
381
|
451
|
if(child.material)
|
382
|
452
|
child.material.specularIntensity = 0;
|
|
@@ -430,6 +500,10 @@ export class Ball extends Entity
|
430
|
500
|
);
|
431
|
501
|
break;
|
432
|
502
|
|
|
503
|
+ case 'hoopready':
|
|
504
|
+ this._hoopPosition.copyFrom(event.detail.position);
|
|
505
|
+ break;
|
|
506
|
+
|
433
|
507
|
case 'netready':
|
434
|
508
|
event.detail.entity.setBall(this._mesh);
|
435
|
509
|
break;
|