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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cyclone-physics",
3
3
  "description": "Pure Javascript physics engine based on http://procyclone.com/",
4
- "version": "1.1.3",
4
+ "version": "1.1.4",
5
5
  "main": "",
6
6
  "author": "James Baicoianu",
7
7
  "license": "MIT",
@@ -322,7 +322,7 @@ elation.require(['physics.common', 'utils.math'], function() {
322
322
  }
323
323
  })();
324
324
 
325
- this.box_box = function() {
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 -= .5 * lastaccel.copy(this.bodies[0].lastacceleration).multiplyScalar(duration).dot(this.normal);
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 += .5 * lastaccel.copy(this.bodies[1].lastacceleration).multiplyScalar(duration).dot(this.normal);
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.25) { // FIXME - velocity threshold should be configurable
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);
@@ -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 = 0;
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