cyclone-physics 1.1.3 → 1.1.4
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.
- package/package.json +1 -1
- package/scripts/collisions.js +234 -7
- package/scripts/common.js +6 -0
- package/scripts/cyclone.js +2 -2
- package/scripts/forces.js +2 -0
package/package.json
CHANGED
package/scripts/collisions.js
CHANGED
|
@@ -322,7 +322,7 @@ elation.require(['physics.common', 'utils.math'], function() {
|
|
|
322
322
|
}
|
|
323
323
|
})();
|
|
324
324
|
|
|
325
|
-
this.
|
|
325
|
+
this.box_box_old = function() {
|
|
326
326
|
// closure scratch variables
|
|
327
327
|
var diff = new THREE.Vector3(),
|
|
328
328
|
thispos = new THREE.Vector3(),
|
|
@@ -484,6 +484,234 @@ elation.require(['physics.common', 'utils.math'], function() {
|
|
|
484
484
|
}
|
|
485
485
|
}();
|
|
486
486
|
|
|
487
|
+
this.box_box = (function() {
|
|
488
|
+
// closure scratch variables
|
|
489
|
+
const scratch = {
|
|
490
|
+
axes: Array(15).fill(null).map(() => new THREE.Vector3()),
|
|
491
|
+
box1Corners: Array(8).fill(null).map(() => new THREE.Vector3()),
|
|
492
|
+
box2Corners: Array(8).fill(null).map(() => new THREE.Vector3()),
|
|
493
|
+
box1Projection: { min: 0, max: 0 },
|
|
494
|
+
box2Projection: { min: 0, max: 0 },
|
|
495
|
+
tmpVec: new THREE.Vector3(),
|
|
496
|
+
tmpQuat: new THREE.Quaternion(),
|
|
497
|
+
contactAxis: new THREE.Vector3(),
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
function getAxesToTest(box1, box2, scratchAxes) {
|
|
501
|
+
const axes = scratchAxes;
|
|
502
|
+
|
|
503
|
+
// Get the three local axes of both boxes in world coordinates
|
|
504
|
+
const box1Axes = getWorldAxes(box1, axes.slice(0, 3));
|
|
505
|
+
const box2Axes = getWorldAxes(box2, axes.slice(3, 6));
|
|
506
|
+
|
|
507
|
+
// Add cross products of edges (9 cross-product axes)
|
|
508
|
+
let axesIndex = 6;
|
|
509
|
+
for (let i = 0; i < 3; i++) {
|
|
510
|
+
for (let j = 0; j < 3; j++) {
|
|
511
|
+
const cross = axes[axesIndex].crossVectors(box1Axes[i], box2Axes[j]);
|
|
512
|
+
if (cross.lengthSq() > 1e-6) { // Avoid zero vectors
|
|
513
|
+
cross.normalize();
|
|
514
|
+
}
|
|
515
|
+
axesIndex++;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return axes;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function getWorldAxes(box, axes) {
|
|
523
|
+
// The orientationWorld is already in world space
|
|
524
|
+
axes[0].set(1, 0, 0).applyQuaternion(box.body.orientationWorld);
|
|
525
|
+
axes[1].set(0, 1, 0).applyQuaternion(box.body.orientationWorld);
|
|
526
|
+
axes[2].set(0, 0, 1).applyQuaternion(box.body.orientationWorld);
|
|
527
|
+
return axes;
|
|
528
|
+
}
|
|
529
|
+
function getPenetrationOnAxis(box1, box2, axis) {
|
|
530
|
+
// Project both boxes onto the axis
|
|
531
|
+
const box1Projection = projectBoxOntoAxis(box1, axis, scratch.box1Corners, scratch.box1Projection);
|
|
532
|
+
const box2Projection = projectBoxOntoAxis(box2, axis, scratch.box2Corners, scratch.box2Projection);
|
|
533
|
+
|
|
534
|
+
// Check if projections overlap
|
|
535
|
+
const overlap = Math.min(box1Projection.max - box2Projection.min, box2Projection.max - box1Projection.min);
|
|
536
|
+
if (box1Projection.max < box2Projection.min || box2Projection.max < box1Projection.min) {
|
|
537
|
+
return false; // Separating axis found
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return overlap; // Return penetration depth on this axis
|
|
541
|
+
}
|
|
542
|
+
function projectBoxOntoAxis(box, axis, corners, projection) {
|
|
543
|
+
getBoxCorners(box, corners);
|
|
544
|
+
|
|
545
|
+
projection.min = Infinity;
|
|
546
|
+
projection.max = -Infinity;
|
|
547
|
+
|
|
548
|
+
for (let corner of corners) {
|
|
549
|
+
const proj = corner.dot(axis);
|
|
550
|
+
projection.min = Math.min(projection.min, proj);
|
|
551
|
+
projection.max = Math.max(projection.max, proj);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return projection;
|
|
555
|
+
}
|
|
556
|
+
function getBoxCorners(box, corners) {
|
|
557
|
+
const halfSize = box.halfsize;
|
|
558
|
+
const offset = box.offset;
|
|
559
|
+
|
|
560
|
+
const scale = box.body.localToWorldScale(scratch.tmpVec.set(1,1,1));
|
|
561
|
+
|
|
562
|
+
// Define the eight corners of the box in local space
|
|
563
|
+
corners[0].set(halfSize.x, halfSize.y, halfSize.z).add(offset).divide(scale);
|
|
564
|
+
corners[1].set(halfSize.x, halfSize.y, -halfSize.z).add(offset).divide(scale);
|
|
565
|
+
corners[2].set(halfSize.x, -halfSize.y, halfSize.z).add(offset).divide(scale);
|
|
566
|
+
corners[3].set(halfSize.x, -halfSize.y, -halfSize.z).add(offset).divide(scale);
|
|
567
|
+
corners[4].set(-halfSize.x, halfSize.y, halfSize.z).add(offset).divide(scale);
|
|
568
|
+
corners[5].set(-halfSize.x, halfSize.y, -halfSize.z).add(offset).divide(scale);
|
|
569
|
+
corners[6].set(-halfSize.x, -halfSize.y, halfSize.z).add(offset).divide(scale);
|
|
570
|
+
corners[7].set(-halfSize.x, -halfSize.y, -halfSize.z).add(offset).divide(scale);
|
|
571
|
+
|
|
572
|
+
// Convert local corners to world space using localToWorld
|
|
573
|
+
for (let i = 0; i < 8; i++) {
|
|
574
|
+
box.body.localToWorldPos(corners[i]);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return corners;
|
|
578
|
+
}
|
|
579
|
+
function generateContacts(box1, box2, minPenetration, contactAxis, contacts) {
|
|
580
|
+
// The contact normal is the axis of minimum penetration
|
|
581
|
+
const contactNormal = contactAxis.clone().normalize();
|
|
582
|
+
|
|
583
|
+
// Find the closest points on the surface of both boxes
|
|
584
|
+
const box1Corners = getBoxCorners(box1, scratch.box1Corners);
|
|
585
|
+
const box2Corners = getBoxCorners(box2, scratch.box2Corners);
|
|
586
|
+
|
|
587
|
+
//const box1Closest = findClosestPointOnBox(box1Corners, contactNormal);
|
|
588
|
+
//const box2Closest = findClosestPointOnBox(box2Corners, contactNormal.clone().negate());
|
|
589
|
+
const box1Closest = findClosestPointOnFace(box1Corners, box2, contactNormal);
|
|
590
|
+
const box2Closest = findClosestPointOnFace(box2Corners, box1, contactNormal);
|
|
591
|
+
|
|
592
|
+
// Contact point is the midpoint between closest points on both boxes
|
|
593
|
+
const contactPoint = box1Closest.clone().add(box2Closest).multiplyScalar(0.5);
|
|
594
|
+
|
|
595
|
+
// Create the contact
|
|
596
|
+
const contact = new elation.physics.contact({
|
|
597
|
+
normal: contactNormal,
|
|
598
|
+
point: contactPoint.clone(),
|
|
599
|
+
penetration: 0, //-minPenetration, // negative value as it's penetration
|
|
600
|
+
//penetration: box1Closest.distanceTo(box2Closest),
|
|
601
|
+
bodies: [box1.body, box2.body]
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
contacts.push(contact);
|
|
605
|
+
return contacts;
|
|
606
|
+
}
|
|
607
|
+
function findClosestPointOnBox(corners, normal) {
|
|
608
|
+
let closestPoint = corners[0];
|
|
609
|
+
let minDistance = closestPoint.dot(normal);
|
|
610
|
+
|
|
611
|
+
for (let i = 1; i < corners.length; i++) {
|
|
612
|
+
const dist = corners[i].dot(normal);
|
|
613
|
+
if (dist < minDistance) {
|
|
614
|
+
closestPoint = corners[i];
|
|
615
|
+
minDistance = dist;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return closestPoint;
|
|
619
|
+
}
|
|
620
|
+
function findClosestPointOnFace(corners, otherBox, normal) {
|
|
621
|
+
let closestPoint = null;
|
|
622
|
+
let minDistance = Infinity;
|
|
623
|
+
|
|
624
|
+
// Check the projection of each corner onto the face of the other box
|
|
625
|
+
for (let i = 0; i < corners.length; i++) {
|
|
626
|
+
const corner = corners[i];
|
|
627
|
+
const projectedPoint = projectPointOntoFace(corner, otherBox, normal);
|
|
628
|
+
|
|
629
|
+
const distance = corner.distanceTo(projectedPoint);
|
|
630
|
+
if (distance < minDistance) {
|
|
631
|
+
closestPoint = projectedPoint;
|
|
632
|
+
minDistance = distance;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return closestPoint;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function projectPointOntoFace(point, box, normal) {
|
|
640
|
+
// First, find the center of the box in world space
|
|
641
|
+
const boxCenter = new THREE.Vector3();
|
|
642
|
+
box.body.localToWorldPos(boxCenter.set(0, 0, 0));
|
|
643
|
+
|
|
644
|
+
// Determine the plane of the face by using the box's normal and a point on the plane
|
|
645
|
+
// (the center of the face, which is aligned with one of the box's axes)
|
|
646
|
+
const halfSize = box.halfsize;
|
|
647
|
+
const offset = new THREE.Vector3().addVectors(box.min, box.max).multiplyScalar(.5);
|
|
648
|
+
const planePoint = new THREE.Vector3();
|
|
649
|
+
const planeNormal = normal.clone().normalize(); // Normal of the face (aligned with one of the box's axes)
|
|
650
|
+
|
|
651
|
+
// We need to determine which face we're projecting onto, so find the direction along the normal
|
|
652
|
+
// Set the point on the plane (face center) by adding/subtracting half the box size along the normal
|
|
653
|
+
for (let i = 0; i < 3; i++) {
|
|
654
|
+
const axisValue = planeNormal.getComponent(i);
|
|
655
|
+
if (axisValue !== 0) {
|
|
656
|
+
planePoint.setComponent(i, boxCenter.getComponent(i) + (axisValue * halfSize.getComponent(i)));
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Now, we project the point onto the plane
|
|
661
|
+
// To do that, find the vector from the point to the plane point
|
|
662
|
+
const pointToPlane = point.clone().sub(planePoint);
|
|
663
|
+
|
|
664
|
+
// Project this vector onto the normal to find how far away the point is from the plane
|
|
665
|
+
const distance = pointToPlane.dot(planeNormal);
|
|
666
|
+
|
|
667
|
+
// Move the point onto the plane by subtracting the distance along the normal
|
|
668
|
+
const projectedPoint = point.clone().sub(planeNormal.multiplyScalar(distance));
|
|
669
|
+
|
|
670
|
+
// Now we need to clamp the projected point to the bounds of the box face
|
|
671
|
+
// For each axis, clamp the value within the bounds of the face
|
|
672
|
+
for (let i = 0; i < 3; i++) {
|
|
673
|
+
const axisValue = planeNormal.getComponent(i);
|
|
674
|
+
if (axisValue === 0) {
|
|
675
|
+
// Clamp the coordinate to be within the face bounds (which are defined by the box size)
|
|
676
|
+
const minVal = boxCenter.getComponent(i) - halfSize.getComponent(i);
|
|
677
|
+
const maxVal = boxCenter.getComponent(i) + halfSize.getComponent(i);
|
|
678
|
+
const value = projectedPoint.getComponent(i);
|
|
679
|
+
projectedPoint.setComponent(i, Math.max(minVal, Math.min(value, maxVal)));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return projectedPoint.add(offset);
|
|
684
|
+
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
return function(box1, box2, contacts, dt) {
|
|
690
|
+
const axes = getAxesToTest(box1, box2, scratch.axes);
|
|
691
|
+
let hasCollision = true;
|
|
692
|
+
let minPenetration = Infinity;
|
|
693
|
+
|
|
694
|
+
// Check for overlap along each axis
|
|
695
|
+
for (let axis of axes) {
|
|
696
|
+
const overlap = getPenetrationOnAxis(box1, box2, axis);
|
|
697
|
+
if (overlap === false) {
|
|
698
|
+
hasCollision = false; // Separating axis found, no collision
|
|
699
|
+
break;
|
|
700
|
+
} else if (overlap < minPenetration) {
|
|
701
|
+
minPenetration = overlap; // Track the minimum penetration
|
|
702
|
+
scratch.contactAxis.copy(axis); // Store the axis with minimum penetration
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (!hasCollision) {
|
|
707
|
+
return false;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// If colliding, generate contact points and return them
|
|
711
|
+
return generateContacts(box1, box2, minPenetration, scratch.contactAxis, contacts);
|
|
712
|
+
}
|
|
713
|
+
})();
|
|
714
|
+
|
|
487
715
|
/* cylinder helpers */
|
|
488
716
|
this.cylinder_sphere = function() {
|
|
489
717
|
// closure scratch variables
|
|
@@ -2163,21 +2391,20 @@ elation.require(['physics.common', 'utils.math'], function() {
|
|
|
2163
2391
|
var lastaccel = new THREE.Vector3();
|
|
2164
2392
|
|
|
2165
2393
|
if (this.bodies[0] && !this.bodies[0].state.sleeping) {
|
|
2166
|
-
velocityFromAccel -=
|
|
2394
|
+
velocityFromAccel -= lastaccel.copy(this.bodies[0].lastacceleration).multiplyScalar(duration).dot(this.normal);
|
|
2167
2395
|
}
|
|
2168
2396
|
if (this.bodies[1] && !this.bodies[1].state.sleeping) {
|
|
2169
|
-
velocityFromAccel +=
|
|
2397
|
+
velocityFromAccel += lastaccel.copy(this.bodies[1].lastacceleration).multiplyScalar(duration).dot(this.normal);
|
|
2170
2398
|
}
|
|
2171
2399
|
|
|
2172
2400
|
var restitution = this.restitution;
|
|
2173
|
-
|
|
2174
|
-
if (Math.abs(this.velocity.y) < 0.
|
|
2401
|
+
|
|
2402
|
+
if (Math.abs(this.velocity.y) < 0.01) { // FIXME - velocity threshold should be configurable
|
|
2175
2403
|
restitution = 0;
|
|
2176
2404
|
}
|
|
2177
|
-
|
|
2405
|
+
|
|
2178
2406
|
this.desiredDeltaVelocity = -this.velocity.y - restitution * (this.velocity.y - velocityFromAccel);
|
|
2179
2407
|
//if (this.desiredDeltaVelocity > 0) this.desiredDeltaVelocity *= -1;
|
|
2180
|
-
//console.log('desiredDeltaV: ' + this.desiredDeltaVelocity);
|
|
2181
2408
|
}
|
|
2182
2409
|
this.calculateInternals = function(duration) {
|
|
2183
2410
|
this.calculateContactMatrix();
|
package/scripts/common.js
CHANGED
|
@@ -15,6 +15,9 @@ elation.require(['engine.external.three.three'], function() {
|
|
|
15
15
|
toJSON() {
|
|
16
16
|
return {x: this._x, y: this._y, z: this._z};
|
|
17
17
|
}
|
|
18
|
+
clone() {
|
|
19
|
+
return super.clone();
|
|
20
|
+
}
|
|
18
21
|
}
|
|
19
22
|
// FIXME - orientation doesn't currently work properly because THREE.Quaternion already uses getters and setters internally
|
|
20
23
|
class CycloneQuaternion extends THREE.Quaternion {
|
|
@@ -34,6 +37,9 @@ elation.require(['engine.external.three.three'], function() {
|
|
|
34
37
|
this.changed = true;
|
|
35
38
|
return super.copy(quat);
|
|
36
39
|
}
|
|
40
|
+
clone() {
|
|
41
|
+
return super.clone();
|
|
42
|
+
}
|
|
37
43
|
set(x, y, z, w) {
|
|
38
44
|
this.changed = true;
|
|
39
45
|
return super.set(x, y, z, w);
|
package/scripts/cyclone.js
CHANGED
|
@@ -7,7 +7,7 @@ elation.require(["physics.common", "physics.processors", "physics.processors.wor
|
|
|
7
7
|
this.position = this.positionWorld = new THREE.Vector3();
|
|
8
8
|
this.orientation = this.orientationWorld = new THREE.Quaternion();
|
|
9
9
|
this.scale = this.scaleWorld = new THREE.Vector3(1, 1, 1);
|
|
10
|
-
this.substep = elation.utils.any(this.args.substep, true);
|
|
10
|
+
this.substep = false; //elation.utils.any(this.args.substep, true);
|
|
11
11
|
this.substepMaxDelta = elation.utils.any(this.args.substepMaxDelta, 20/1000);
|
|
12
12
|
this.substepMaxSteps = elation.utils.any(this.args.substepMaxSteps, 4);
|
|
13
13
|
this.processortype = elation.utils.any(this.args.processortype, 'cpu');
|
|
@@ -39,7 +39,7 @@ elation.require(["physics.common", "physics.processors", "physics.processors.wor
|
|
|
39
39
|
steps = Math.min(Math.round(t / this.substepMaxDelta), this.substepMaxSteps);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
var step =
|
|
42
|
+
var step = 1;
|
|
43
43
|
while (t > 0) {
|
|
44
44
|
var steptime = (step < steps ? Math.min(t, this.substepMaxDelta) : t);
|
|
45
45
|
|
package/scripts/forces.js
CHANGED
|
@@ -415,6 +415,8 @@ elation.require(['physics.common'], function() {
|
|
|
415
415
|
var _tmpvec2 = new THREE.Vector3();
|
|
416
416
|
|
|
417
417
|
this.apply = function() {
|
|
418
|
+
if (this.disabled) return;
|
|
419
|
+
|
|
418
420
|
var lws = this.body.localToWorldPos(_tmpvec1.copy(this.connectionpoint));
|
|
419
421
|
var ows = (this.other ? this.other.localToWorldPos(_tmpvec2.copy(this.otherconnectionpoint)) : this.anchor);
|
|
420
422
|
|