Skip to content

Commit 5b89601

Browse files
authored
Merge pull request #113 from wmurphyrd/stretch-physics-complex
Stretch complex physics bodies and nested entity bodies
2 parents 2b59338 + 28ef310 commit 5b89601

File tree

7 files changed

+239
-82
lines changed

7 files changed

+239
-82
lines changed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,11 @@ require('super-hands');
117117

118118
Master branch
119119

120-
* No changes
120+
* Improved nested entity handling: only one component can react to each
121+
gesture event.
122+
* Improved stretching of complex physics bodies: all shapes, child entity
123+
shapes, and offsets are updated
124+
121125

122126
Master branch features can be tested using:
123127

@@ -510,11 +514,21 @@ Makes and entity rescale while grabbed by both controllers as they are moved clo
510514
| endButtons | Which button events to accept to end stretch | `[]` |
511515
| usePhysics | Whether to update physics body shapes with scale changes, 'ifavailable' or 'never' | 'ifavailable' |
512516
| invert | Reverse the direction of scaling in relation to controller movement | `false` |
517+
| phyicsUpdateRate | Milliseconds between each update to the physics bodies of a stretched entity | 100 |
513518

514519
The default for `startButtons` and `endButtons` is to accept any button
515520
recognized by `super-hands` `stretchStartButtons` and `stretchEndButtons`.
516521

517-
There is no CANNON api method for updating physics body scale, but `stretchable` will manually rescale basic shapes. Currently rescalable shapes are: box and sphere.
522+
There is no CANNON API method for updating physics body scale, but `stretchable`
523+
will manually rescale shapes and offsets for stretched entity body and
524+
all descendent entity bodies.
525+
This update is throttled and will occur no more than once every
526+
`physicsUpdateRate` milliseconds to improve performance. Set this to a smaller
527+
number to increase the physics simulation fidelity.
528+
Currently rescalable shapes are: box and sphere. At present, this rescaling
529+
is only possible on when using the `'local'` physics driver. If using another
530+
driver, setting `usePhysics: never` will avoid errors but also cause
531+
loss of sync between stretched entities' appearance and behavior.
518532

519533
#### States
520534

dist/super-hands.js

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,7 +1323,8 @@ const buttonCore = require('./prototypes/buttons-proto.js');
13231323
AFRAME.registerComponent('stretchable', inherit({}, buttonCore, {
13241324
schema: {
13251325
usePhysics: { default: 'ifavailable' },
1326-
invert: { default: false }
1326+
invert: { default: false },
1327+
physicsUpdateRate: { default: 100 }
13271328
},
13281329
init: function () {
13291330
this.STRETCHED_STATE = 'stretched';
@@ -1342,8 +1343,10 @@ AFRAME.registerComponent('stretchable', inherit({}, buttonCore, {
13421343
this.el.addEventListener(this.STRETCH_EVENT, this.start);
13431344
this.el.addEventListener(this.UNSTRETCH_EVENT, this.end);
13441345
},
1345-
update: function (oldDat) {},
1346-
tick: function () {
1346+
update: function (oldDat) {
1347+
this.updateBodies = AFRAME.utils.throttleTick(this._updateBodies, this.data.physicsUpdateRate, this);
1348+
},
1349+
tick: function (time, timeDelta) {
13471350
if (!this.stretched) {
13481351
return;
13491352
}
@@ -1356,31 +1359,14 @@ AFRAME.registerComponent('stretchable', inherit({}, buttonCore, {
13561359
deltaStretch = Math.pow(currentStretch / this.previousStretch, this.data.invert ? -1 : 1);
13571360
}
13581361
this.previousStretch = currentStretch;
1362+
if (this.previousPhysicsStretch == null) {
1363+
// establish correct baseline even if throttled function isn't called
1364+
this.previousPhysicsStretch = currentStretch;
1365+
}
13591366
this.scale.multiplyScalar(deltaStretch);
13601367
this.el.setAttribute('scale', this.scale);
1361-
// force scale update for physics body
1362-
if (this.el.body && this.data.usePhysics !== 'never') {
1363-
var physicsShape = this.el.body.shapes[0];
1364-
if (physicsShape.halfExtents) {
1365-
physicsShape.halfExtents.scale(deltaStretch, physicsShape.halfExtents);
1366-
physicsShape.updateConvexPolyhedronRepresentation();
1367-
} else if (physicsShape.radius) {
1368-
physicsShape.radius *= deltaStretch;
1369-
physicsShape.updateBoundingSphereRadius();
1370-
// This doesn't update the cone size - can't find right update function
1371-
// } else if (physicsShape.radiusTop && physicsShape.radiusBottom &&
1372-
// physicsShape.height) {
1373-
// physicsShape.height *= deltaStretch;
1374-
// physicsShape.radiusTop *= deltaStretch;
1375-
// physicsShape.radiusBottom *= deltaStretch;
1376-
// physicsShape.updateBoundingSphereRadius();
1377-
} else if (!this.shapeWarned) {
1378-
console.warn('Unable to stretch physics body: unsupported shape');
1379-
this.shapeWarned = true;
1380-
// todo: suport more shapes
1381-
}
1382-
this.el.body.updateBoundingRadius();
1383-
}
1368+
// scale update for all nested physics bodies (throttled)
1369+
this.updateBodies(time, timeDelta);
13841370
},
13851371
remove: function () {
13861372
this.el.removeEventListener(this.STRETCH_EVENT, this.start);
@@ -1394,6 +1380,7 @@ AFRAME.registerComponent('stretchable', inherit({}, buttonCore, {
13941380
if (this.stretchers.length === 2) {
13951381
this.stretched = true;
13961382
this.previousStretch = null;
1383+
this.previousPhysicsStretch = null;
13971384
this.el.addState(this.STRETCHED_STATE);
13981385
}
13991386
if (evt.preventDefault) {
@@ -1409,10 +1396,54 @@ AFRAME.registerComponent('stretchable', inherit({}, buttonCore, {
14091396
this.stretchers.splice(stretcherIndex, 1);
14101397
this.stretched = false;
14111398
this.el.removeState(this.STRETCHED_STATE);
1399+
// override throttle to push last stretch to physics bodies
1400+
this._updateBodies();
14121401
}
14131402
if (evt.preventDefault) {
14141403
evt.preventDefault();
14151404
}
1405+
},
1406+
_updateBodies: function () {
1407+
if (!this.el.body || this.data.usePhysics === 'never') {
1408+
return;
1409+
}
1410+
const currentStretch = this.previousStretch; // last visible geometry stretch
1411+
let deltaStretch = 1;
1412+
if (this.previousPhysicsStretch !== null && currentStretch > 0) {
1413+
deltaStretch = Math.pow(currentStretch / this.previousPhysicsStretch, this.data.invert ? -1 : 1);
1414+
}
1415+
this.previousPhysicsStretch = currentStretch;
1416+
if (deltaStretch === 1) {
1417+
return;
1418+
}
1419+
for (let c of this.el.children) {
1420+
this.stretchBody(c, deltaStretch);
1421+
}
1422+
this.stretchBody(this.el, deltaStretch);
1423+
},
1424+
stretchBody: function (el, deltaStretch) {
1425+
if (!el.body) {
1426+
return;
1427+
}
1428+
let physicsShape;
1429+
let offset;
1430+
for (let i = 0; i < el.body.shapes.length; i++) {
1431+
physicsShape = el.body.shapes[i];
1432+
if (physicsShape.halfExtents) {
1433+
physicsShape.halfExtents.scale(deltaStretch, physicsShape.halfExtents);
1434+
physicsShape.updateConvexPolyhedronRepresentation();
1435+
} else if (physicsShape.radius) {
1436+
physicsShape.radius *= deltaStretch;
1437+
physicsShape.updateBoundingSphereRadius();
1438+
} else if (!this.shapeWarned) {
1439+
console.warn('Unable to stretch physics body: unsupported shape');
1440+
this.shapeWarned = true;
1441+
}
1442+
// also move offset to match scale change
1443+
offset = el.body.shapeOffsets[i];
1444+
offset.scale(deltaStretch, offset);
1445+
}
1446+
el.body.updateBoundingRadius();
14161447
}
14171448
}));
14181449

dist/super-hands.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/build.js

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2774,7 +2774,8 @@ const buttonCore = require('./prototypes/buttons-proto.js');
27742774
AFRAME.registerComponent('stretchable', inherit({}, buttonCore, {
27752775
schema: {
27762776
usePhysics: { default: 'ifavailable' },
2777-
invert: { default: false }
2777+
invert: { default: false },
2778+
physicsUpdateRate: { default: 100 }
27782779
},
27792780
init: function () {
27802781
this.STRETCHED_STATE = 'stretched';
@@ -2793,8 +2794,10 @@ AFRAME.registerComponent('stretchable', inherit({}, buttonCore, {
27932794
this.el.addEventListener(this.STRETCH_EVENT, this.start);
27942795
this.el.addEventListener(this.UNSTRETCH_EVENT, this.end);
27952796
},
2796-
update: function (oldDat) {},
2797-
tick: function () {
2797+
update: function (oldDat) {
2798+
this.updateBodies = AFRAME.utils.throttleTick(this._updateBodies, this.data.physicsUpdateRate, this);
2799+
},
2800+
tick: function (time, timeDelta) {
27982801
if (!this.stretched) {
27992802
return;
28002803
}
@@ -2807,31 +2810,14 @@ AFRAME.registerComponent('stretchable', inherit({}, buttonCore, {
28072810
deltaStretch = Math.pow(currentStretch / this.previousStretch, this.data.invert ? -1 : 1);
28082811
}
28092812
this.previousStretch = currentStretch;
2813+
if (this.previousPhysicsStretch == null) {
2814+
// establish correct baseline even if throttled function isn't called
2815+
this.previousPhysicsStretch = currentStretch;
2816+
}
28102817
this.scale.multiplyScalar(deltaStretch);
28112818
this.el.setAttribute('scale', this.scale);
2812-
// force scale update for physics body
2813-
if (this.el.body && this.data.usePhysics !== 'never') {
2814-
var physicsShape = this.el.body.shapes[0];
2815-
if (physicsShape.halfExtents) {
2816-
physicsShape.halfExtents.scale(deltaStretch, physicsShape.halfExtents);
2817-
physicsShape.updateConvexPolyhedronRepresentation();
2818-
} else if (physicsShape.radius) {
2819-
physicsShape.radius *= deltaStretch;
2820-
physicsShape.updateBoundingSphereRadius();
2821-
// This doesn't update the cone size - can't find right update function
2822-
// } else if (physicsShape.radiusTop && physicsShape.radiusBottom &&
2823-
// physicsShape.height) {
2824-
// physicsShape.height *= deltaStretch;
2825-
// physicsShape.radiusTop *= deltaStretch;
2826-
// physicsShape.radiusBottom *= deltaStretch;
2827-
// physicsShape.updateBoundingSphereRadius();
2828-
} else if (!this.shapeWarned) {
2829-
console.warn('Unable to stretch physics body: unsupported shape');
2830-
this.shapeWarned = true;
2831-
// todo: suport more shapes
2832-
}
2833-
this.el.body.updateBoundingRadius();
2834-
}
2819+
// scale update for all nested physics bodies (throttled)
2820+
this.updateBodies(time, timeDelta);
28352821
},
28362822
remove: function () {
28372823
this.el.removeEventListener(this.STRETCH_EVENT, this.start);
@@ -2845,6 +2831,7 @@ AFRAME.registerComponent('stretchable', inherit({}, buttonCore, {
28452831
if (this.stretchers.length === 2) {
28462832
this.stretched = true;
28472833
this.previousStretch = null;
2834+
this.previousPhysicsStretch = null;
28482835
this.el.addState(this.STRETCHED_STATE);
28492836
}
28502837
if (evt.preventDefault) {
@@ -2860,10 +2847,54 @@ AFRAME.registerComponent('stretchable', inherit({}, buttonCore, {
28602847
this.stretchers.splice(stretcherIndex, 1);
28612848
this.stretched = false;
28622849
this.el.removeState(this.STRETCHED_STATE);
2850+
// override throttle to push last stretch to physics bodies
2851+
this._updateBodies();
28632852
}
28642853
if (evt.preventDefault) {
28652854
evt.preventDefault();
28662855
}
2856+
},
2857+
_updateBodies: function () {
2858+
if (!this.el.body || this.data.usePhysics === 'never') {
2859+
return;
2860+
}
2861+
const currentStretch = this.previousStretch; // last visible geometry stretch
2862+
let deltaStretch = 1;
2863+
if (this.previousPhysicsStretch !== null && currentStretch > 0) {
2864+
deltaStretch = Math.pow(currentStretch / this.previousPhysicsStretch, this.data.invert ? -1 : 1);
2865+
}
2866+
this.previousPhysicsStretch = currentStretch;
2867+
if (deltaStretch === 1) {
2868+
return;
2869+
}
2870+
for (let c of this.el.children) {
2871+
this.stretchBody(c, deltaStretch);
2872+
}
2873+
this.stretchBody(this.el, deltaStretch);
2874+
},
2875+
stretchBody: function (el, deltaStretch) {
2876+
if (!el.body) {
2877+
return;
2878+
}
2879+
let physicsShape;
2880+
let offset;
2881+
for (let i = 0; i < el.body.shapes.length; i++) {
2882+
physicsShape = el.body.shapes[i];
2883+
if (physicsShape.halfExtents) {
2884+
physicsShape.halfExtents.scale(deltaStretch, physicsShape.halfExtents);
2885+
physicsShape.updateConvexPolyhedronRepresentation();
2886+
} else if (physicsShape.radius) {
2887+
physicsShape.radius *= deltaStretch;
2888+
physicsShape.updateBoundingSphereRadius();
2889+
} else if (!this.shapeWarned) {
2890+
console.warn('Unable to stretch physics body: unsupported shape');
2891+
this.shapeWarned = true;
2892+
}
2893+
// also move offset to match scale change
2894+
offset = el.body.shapeOffsets[i];
2895+
offset.scale(deltaStretch, offset);
2896+
}
2897+
el.body.updateBoundingRadius();
28672898
}
28682899
}));
28692900

machinima_tests/super_hands/progressive-controls-machinima.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const machinima = require('aframe-machinima-testing')
44

55
suite('progressive-controls touch interactions', function () {
66
setup(function (done) {
7+
this.timeout(0)
78
machinima.setupScene('progressive-hands.html')
89
this.scene = document.querySelector('a-scene')
910
this.scene.addEventListener('loaded', e => {

0 commit comments

Comments
 (0)