threejs-cannones-rigger 1.0.1 → 1.0.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/dist/threejs-cannones-rigger.cjs +1 -1
- package/dist/threejs-cannones-rigger.js +36 -21
- package/package.json +1 -1
- package/readme.md +61 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("cannon-es"),i=require("three"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("cannon-es"),i=require("three"),v=require("threejs-cannones-tube"),B={x:0,box:1,sphere:2,compound:3,lock:4,hinge:5,point:6,dist:7,sync:8,tube:9,custom:10};function b(h){return h.reduce((n,t,e)=>n|t<<e,0)}const w=new i.Vector3,y=new i.Vector3,Q=new i.Quaternion,D=new i.Quaternion;class P{constructor(n,t){this.world=n,t&&this.rigScene(t)}obj2bod=new Map;constraints=[];customId2ConstraintFactory=new Map;customConstraints=new Map;registerCustomConstraint(n,t){this.customId2ConstraintFactory.set(n,t)}getConstraintByName(n){return this.constraints.find(t=>t.name==n)}getCustomConstraint(n){return this.customConstraints.get(n)}getCableConstraint(n){return this.getConstraintByName(n)}getSyncConstraint(n){return this.getConstraintByName(n)}getLockConstraint(n){return this.getConstraintByName(n)?.cannonConstraint}getHingeConstraint(n){return this.getConstraintByName(n)?.cannonConstraint}getPointConstraint(n){return this.getConstraintByName(n)?.cannonConstraint}getDistanceConstraint(n){return this.getConstraintByName(n)?.cannonConstraint}getBodyByName(n){for(const[t,e]of this.obj2bod.entries())if(t.userData.name===n)return e}rigScene(n){n.traverse(t=>{let e;typeof t.userData.threejscannones_type=="string"&&(t.userData.threejscannones_type=B[t.userData.threejscannones_type]),t.userData.threejscannones_cgroup&&(t.userData.threejscannones_cgroup=b(t.userData.threejscannones_cgroup)),t.userData.threejscannones_cwith&&(t.userData.threejscannones_cwith=b(t.userData.threejscannones_cwith)),t.userData.threejscannones_type==1?e=this.createCollider(new c.Box(new c.Vec3(t.scale.x,t.scale.y,t.scale.z)),t):t.userData.threejscannones_type==2?e=this.createCollider(new c.Sphere(t.scale.x),t):t.userData.threejscannones_type==3&&(e=this.createCollider(void 0,t),this.addCompoundShapes(e,t))}),n.traverse(t=>{const e=this.getBodyByName(t.userData.threejscannones_A?.name),o=this.getBodyByName(t.userData.threejscannones_B?.name);let s;switch(t.userData.threejscannones_type){case 7:s=this.createDistanceConstraint(e,o);break;case 6:s=this.createPointConstraint(e,o,t);break;case 5:s=this.createHingeConstraint(e,o,t);break;case 4:s=this.createLockConstraint(e,o);break;case 8:const a=this.getBodyByName(t.userData.threejscannones_syncSource?.name);this.createSyncBetween(t,a);break;case 9:this.createCable(t,e,o);break;case 10:this.createCustomConstraint(t,e,o)}s&&this.constraints.push(new C(t,s))})}clear(){for(w.set(0,0,0),y.set(0,0,0),Q.set(0,0,0,0),D.set(0,0,0,0);this.constraints.length;)this.constraints.pop().removeFrom(this.world);for(const[n,t]of this.obj2bod.entries())this.world.removeBody(t);this.obj2bod.clear()}addCompoundShapes(n,t){const e=t.children;t.updateMatrixWorld();const o=t.getWorldPosition(new i.Vector3),s=t.getWorldQuaternion(new i.Quaternion),a=s.clone().invert();for(const r of e){r.updateMatrixWorld();const u=r.getWorldPosition(new i.Vector3),m=r.getWorldQuaternion(new i.Quaternion),d=r.getWorldScale(new i.Vector3),p=u.clone().sub(o).applyQuaternion(a),l=a.clone().multiply(m),T=new c.Vec3(p.x,p.y,p.z),k=new c.Quaternion(l.x,l.y,l.z,l.w),x=new c.Vec3(d.x,d.y,d.z),j=new c.Box(x);n.addShape(j,T,k)}return n.position.copy(new c.Vec3(o.x,o.y,o.z)),n.quaternion.copy(new c.Quaternion(s.x,s.y,s.z,s.w)),n}createCustomConstraint(n,t,e){let o=n.userData.threejscannones_cgroup??1,s=n.userData.threejscannones_cwith??1;const a=n.userData.threejscannones_customId;if(!a)throw new Error("A custom constraint MUST have an id...");const r=this.customId2ConstraintFactory.get(a);if(!r)throw new Error(`Custom constraint with id ${a} not found. Dis you forgot to call registerCustomConstraint?`);const u=r({obj:n,collisionGroup:o,collisionMask:s,A:t,B:e});this.customConstraints.set(n.userData.name,u)}createCable(n,t,e){let o=1,s=!1,a=new i.Vector3;n.children.length>1&&(n.children[0].getWorldPosition(w),n.children[1].localToWorld(y),o=w.distanceTo(y),s=!0,a.copy(y));const r=new v.CannonTubeRig(o,20,.1,8);r.material=n instanceof i.Mesh?n.material:new i.MeshNormalMaterial,n.parent?.add(r),s&&(r.position.copy(w),r.lookAt(y)),r.addToPhysicalWorld(this.world),r.syncRig(),this.constraints.push(new g(this.world,n,r,t,e))}createSyncBetween(n,t){if(!t){console.warn(`Object ${n.name} points to a non existent collider for it's sync constrain.`);return}const e=new f(n,t);this.constraints.push(e)}createLockConstraint(n,t){const e=new c.LockConstraint(n,t);return e.collideConnected=!1,this.world.addConstraint(e),e}createHingeConstraint(n,t,e){const o=e.getWorldPosition(new i.Vector3),s=new i.Vector3(0,1,0).applyQuaternion(e.getWorldQuaternion(new i.Quaternion)).normalize(),a=new c.Vec3(o.x,o.y,o.z),r=new c.Vec3(s.x,s.y,s.z),u=n.pointToLocalFrame(a),m=t.pointToLocalFrame(a),d=n.vectorToLocalFrame(r),p=t.vectorToLocalFrame(r),l=new c.HingeConstraint(n,t,{pivotA:u,pivotB:m,axisA:d,axisB:p,collideConnected:!1});return this.world.addConstraint(l),l}createPointConstraint(n,t,e){const o=e.getWorldPosition(new i.Vector3),s=n.pointToLocalFrame(new c.Vec3(o.x,o.y,o.z)),a=t.pointToLocalFrame(new c.Vec3(o.x,o.y,o.z)),r=new c.PointToPointConstraint(n,s,t,a);return this.world.addConstraint(r),r}createDistanceConstraint(n,t){const e=new c.DistanceConstraint(n,t);return this.world.addConstraint(e),e}createCollider(n,t){t.visible=!1;let e=t.userData.threejscannones_cgroup??1,o=t.userData.threejscannones_cwith??1;const s=new c.Body({shape:n,mass:t.userData.threejscannones_mass??0,collisionFilterMask:o,collisionFilterGroup:e}),a=t.getWorldPosition(new i.Vector3);s.position.set(a.x,a.y,a.z);const r=t.getWorldQuaternion(new i.Quaternion);return s.quaternion.set(r.x,r.y,r.z,r.w),this.world.addBody(s),this.obj2bod.set(t,s),s}update(n){for(let t=0;t<this.constraints.length;t++)this.constraints[t].update(n)}}class C{constructor(n,t){this.obj=n,this.cannonConstraint=t}enable(){this.cannonConstraint?.enable()}disable(){this.cannonConstraint?.disable()}update(n){}get name(){return this.obj.userData.name}removeFrom(n){this.cannonConstraint&&n.removeConstraint(this.cannonConstraint)}}class f extends C{constructor(n,t){super(n),this.body=t}offsetPos=new i.Vector3;offsetQuat=new i.Quaternion;hasInit=!1;initOffsets(){const n=new i.Vector3(this.body.position.x,this.body.position.y,this.body.position.z),t=new i.Quaternion(this.body.quaternion.x,this.body.quaternion.y,this.body.quaternion.z,this.body.quaternion.w),e=new i.Vector3,o=new i.Quaternion;this.obj.getWorldPosition(e),this.obj.getWorldQuaternion(o),this.offsetPos.copy(e).sub(n).applyQuaternion(t.clone().invert()),this.offsetQuat.copy(t.clone().invert().multiply(o)),this.hasInit=!0}update(){this.hasInit||this.initOffsets();const n=new i.Vector3(this.body.position.x,this.body.position.y,this.body.position.z),t=new i.Quaternion(this.body.quaternion.x,this.body.quaternion.y,this.body.quaternion.z,this.body.quaternion.w),e=t.clone().multiply(this.offsetQuat),o=this.offsetPos.clone().applyQuaternion(t).add(n);if(this.obj.parent){this.obj.parent.updateMatrixWorld(!0);const s=new i.Matrix4().compose(o,e,new i.Vector3(1,1,1)),a=new i.Matrix4().copy(this.obj.parent.matrixWorld).invert();s.premultiply(a),s.decompose(this.obj.position,this.obj.quaternion,new i.Vector3)}else this.obj.position.copy(o),this.obj.quaternion.copy(e)}}class g extends C{constructor(n,t,e,o,s){super(t),this.world=n,this.cable=e,o&&this.lockHeadTo(o),s&&this.lockTailTo(s)}lockToA;lockToB;lockXTo(n,t,e){let o;return n&&this.world.removeConstraint(n),e&&(o=new c.PointToPointConstraint(t,new c.Vec3,e,e.pointToLocalFrame(t.position)),o.collideConnected=!1,this.world.addConstraint(o)),o}lockHeadTo(n){this.lockToA=this.lockXTo(this.lockToA,this.cable.head,n)}lockTailTo(n){this.lockToB=this.lockXTo(this.lockToB,this.cable.tail,n)}enable(){this.lockToA?.enable(),this.lockToB?.enable(),this.cable.constraints.forEach(n=>n.enable())}disable(){this.lockToA?.disable(),this.lockToB?.disable(),this.cable.constraints.forEach(n=>n.disable())}update(){this.cable.syncRig()}removeFrom(n){this.lockToA&&(n.removeConstraint(this.lockToA),this.lockToA=void 0),this.lockToB&&(n.removeConstraint(this.lockToB),this.lockToA=void 0),this.cable.removeFromPhysicalWorld(n),super.removeFrom(n)}}exports.CableConstraint=g;exports.SyncConstraint=f;exports.ThreeJsCannonEsConstraint=C;exports.ThreeJsCannonEsSceneRigger=P;
|
|
@@ -1,11 +1,26 @@
|
|
|
1
|
-
import { Box as f, Vec3 as l, Sphere as W, Quaternion as g, LockConstraint as
|
|
1
|
+
import { Box as f, Vec3 as l, Sphere as W, Quaternion as g, LockConstraint as _, HingeConstraint as P, PointToPointConstraint as x, DistanceConstraint as Q, Body as A } from "cannon-es";
|
|
2
2
|
import { Vector3 as r, Quaternion as c, MeshNormalMaterial as z, Mesh as F, Matrix4 as T } from "three";
|
|
3
3
|
import { CannonTubeRig as S } from "threejs-cannones-tube";
|
|
4
|
+
const M = {
|
|
5
|
+
// in blender 5+ enum properties are no longer exported as int
|
|
6
|
+
x: 0,
|
|
7
|
+
box: 1,
|
|
8
|
+
sphere: 2,
|
|
9
|
+
compound: 3,
|
|
10
|
+
lock: 4,
|
|
11
|
+
hinge: 5,
|
|
12
|
+
point: 6,
|
|
13
|
+
dist: 7,
|
|
14
|
+
sync: 8,
|
|
15
|
+
tube: 9,
|
|
16
|
+
custom: 10
|
|
17
|
+
/* Custom */
|
|
18
|
+
};
|
|
4
19
|
function k(d) {
|
|
5
20
|
return d.reduce((n, t, e) => n | t << e, 0);
|
|
6
21
|
}
|
|
7
|
-
const m = new r(),
|
|
8
|
-
class
|
|
22
|
+
const m = new r(), w = new r(), q = new c(), L = new c();
|
|
23
|
+
class R {
|
|
9
24
|
/**
|
|
10
25
|
* Creates a new ThreeJsCannonEsSceneRigger.
|
|
11
26
|
* @param world The Cannon-es physics world.
|
|
@@ -100,7 +115,7 @@ class O {
|
|
|
100
115
|
rigScene(n) {
|
|
101
116
|
n.traverse((t) => {
|
|
102
117
|
let e;
|
|
103
|
-
t.userData.threejscannones_cgroup && (t.userData.threejscannones_cgroup = k(t.userData.threejscannones_cgroup)), t.userData.threejscannones_cwith && (t.userData.threejscannones_cwith = k(t.userData.threejscannones_cwith)), t.userData.threejscannones_type == 1 ? e = this.createCollider(new f(new l(t.scale.x, t.scale.y, t.scale.z)), t) : t.userData.threejscannones_type == 2 ? e = this.createCollider(new W(t.scale.x), t) : t.userData.threejscannones_type == 3 && (e = this.createCollider(void 0, t), this.addCompoundShapes(e, t));
|
|
118
|
+
typeof t.userData.threejscannones_type == "string" && (t.userData.threejscannones_type = M[t.userData.threejscannones_type]), t.userData.threejscannones_cgroup && (t.userData.threejscannones_cgroup = k(t.userData.threejscannones_cgroup)), t.userData.threejscannones_cwith && (t.userData.threejscannones_cwith = k(t.userData.threejscannones_cwith)), t.userData.threejscannones_type == 1 ? e = this.createCollider(new f(new l(t.scale.x, t.scale.y, t.scale.z)), t) : t.userData.threejscannones_type == 2 ? e = this.createCollider(new W(t.scale.x), t) : t.userData.threejscannones_type == 3 && (e = this.createCollider(void 0, t), this.addCompoundShapes(e, t));
|
|
104
119
|
}), n.traverse((t) => {
|
|
105
120
|
const e = this.getBodyByName(t.userData.threejscannones_A?.name), o = this.getBodyByName(t.userData.threejscannones_B?.name);
|
|
106
121
|
let s;
|
|
@@ -134,7 +149,7 @@ class O {
|
|
|
134
149
|
* Removes all (known) created bodies and constraints from the world and clears internal state.
|
|
135
150
|
*/
|
|
136
151
|
clear() {
|
|
137
|
-
for (m.set(0, 0, 0),
|
|
152
|
+
for (m.set(0, 0, 0), w.set(0, 0, 0), q.set(0, 0, 0, 0), L.set(0, 0, 0, 0); this.constraints.length; )
|
|
138
153
|
this.constraints.pop().removeFrom(this.world);
|
|
139
154
|
for (const [n, t] of this.obj2bod.entries())
|
|
140
155
|
this.world.removeBody(t);
|
|
@@ -146,8 +161,8 @@ class O {
|
|
|
146
161
|
const o = t.getWorldPosition(new r()), s = t.getWorldQuaternion(new c()), a = s.clone().invert();
|
|
147
162
|
for (const i of e) {
|
|
148
163
|
i.updateMatrixWorld();
|
|
149
|
-
const u = i.getWorldPosition(new r()), C = i.getWorldQuaternion(new c()), p = i.getWorldScale(new r()),
|
|
150
|
-
n.addShape(D,
|
|
164
|
+
const u = i.getWorldPosition(new r()), C = i.getWorldQuaternion(new c()), p = i.getWorldScale(new r()), y = u.clone().sub(o).applyQuaternion(a), h = a.clone().multiply(C), v = new l(y.x, y.y, y.z), B = new g(h.x, h.y, h.z, h.w), j = new l(p.x, p.y, p.z), D = new f(j);
|
|
165
|
+
n.addShape(D, v, B);
|
|
151
166
|
}
|
|
152
167
|
return n.position.copy(new l(o.x, o.y, o.z)), n.quaternion.copy(new g(s.x, s.y, s.z, s.w)), n;
|
|
153
168
|
}
|
|
@@ -176,7 +191,7 @@ class O {
|
|
|
176
191
|
*/
|
|
177
192
|
createCable(n, t, e) {
|
|
178
193
|
let o = 1, s = !1, a = new r();
|
|
179
|
-
n.children.length > 1 && (n.children[0].getWorldPosition(m), n.children[1].localToWorld(
|
|
194
|
+
n.children.length > 1 && (n.children[0].getWorldPosition(m), n.children[1].localToWorld(w), o = m.distanceTo(w), s = !0, a.copy(w));
|
|
180
195
|
const i = new S(
|
|
181
196
|
o,
|
|
182
197
|
// length in world units
|
|
@@ -187,7 +202,7 @@ class O {
|
|
|
187
202
|
8
|
|
188
203
|
// resolution along the radius
|
|
189
204
|
);
|
|
190
|
-
i.material = n instanceof F ? n.material : new z(), n.parent?.add(i), s && (i.position.copy(m), i.lookAt(
|
|
205
|
+
i.material = n instanceof F ? n.material : new z(), n.parent?.add(i), s && (i.position.copy(m), i.lookAt(w)), i.addToPhysicalWorld(this.world), i.syncRig(), this.constraints.push(new I(this.world, n, i, t, e));
|
|
191
206
|
}
|
|
192
207
|
/**
|
|
193
208
|
* Synchronizes a Three.js object with a Cannon body.
|
|
@@ -199,7 +214,7 @@ class O {
|
|
|
199
214
|
console.warn(`Object ${n.name} points to a non existent collider for it's sync constrain.`);
|
|
200
215
|
return;
|
|
201
216
|
}
|
|
202
|
-
const e = new
|
|
217
|
+
const e = new N(n, t);
|
|
203
218
|
this.constraints.push(e);
|
|
204
219
|
}
|
|
205
220
|
/**
|
|
@@ -209,7 +224,7 @@ class O {
|
|
|
209
224
|
* @returns The created LockConstraint.
|
|
210
225
|
*/
|
|
211
226
|
createLockConstraint(n, t) {
|
|
212
|
-
const e = new
|
|
227
|
+
const e = new _(n, t);
|
|
213
228
|
return e.collideConnected = !1, this.world.addConstraint(e), e;
|
|
214
229
|
}
|
|
215
230
|
/**
|
|
@@ -220,11 +235,11 @@ class O {
|
|
|
220
235
|
* @returns The created HingeConstraint.
|
|
221
236
|
*/
|
|
222
237
|
createHingeConstraint(n, t, e) {
|
|
223
|
-
const o = e.getWorldPosition(new r()), s = new r(0, 1, 0).applyQuaternion(e.getWorldQuaternion(new c())).normalize(), a = new l(o.x, o.y, o.z), i = new l(s.x, s.y, s.z), u = n.pointToLocalFrame(a), C = t.pointToLocalFrame(a), p = n.vectorToLocalFrame(i),
|
|
238
|
+
const o = e.getWorldPosition(new r()), s = new r(0, 1, 0).applyQuaternion(e.getWorldQuaternion(new c())).normalize(), a = new l(o.x, o.y, o.z), i = new l(s.x, s.y, s.z), u = n.pointToLocalFrame(a), C = t.pointToLocalFrame(a), p = n.vectorToLocalFrame(i), y = t.vectorToLocalFrame(i), h = new P(n, t, {
|
|
224
239
|
pivotA: u,
|
|
225
240
|
pivotB: C,
|
|
226
241
|
axisA: p,
|
|
227
|
-
axisB:
|
|
242
|
+
axisB: y,
|
|
228
243
|
collideConnected: !1
|
|
229
244
|
//maxForce: 1e9,
|
|
230
245
|
});
|
|
@@ -238,7 +253,7 @@ class O {
|
|
|
238
253
|
* @returns The created PointToPointConstraint.
|
|
239
254
|
*/
|
|
240
255
|
createPointConstraint(n, t, e) {
|
|
241
|
-
const o = e.getWorldPosition(new r()), s = n.pointToLocalFrame(new l(o.x, o.y, o.z)), a = t.pointToLocalFrame(new l(o.x, o.y, o.z)), i = new
|
|
256
|
+
const o = e.getWorldPosition(new r()), s = n.pointToLocalFrame(new l(o.x, o.y, o.z)), a = t.pointToLocalFrame(new l(o.x, o.y, o.z)), i = new x(n, s, t, a);
|
|
242
257
|
return this.world.addConstraint(i), i;
|
|
243
258
|
}
|
|
244
259
|
/**
|
|
@@ -248,7 +263,7 @@ class O {
|
|
|
248
263
|
* @returns The created DistanceConstraint.
|
|
249
264
|
*/
|
|
250
265
|
createDistanceConstraint(n, t) {
|
|
251
|
-
const e = new
|
|
266
|
+
const e = new Q(n, t);
|
|
252
267
|
return this.world.addConstraint(e), e;
|
|
253
268
|
}
|
|
254
269
|
/**
|
|
@@ -298,7 +313,7 @@ class b {
|
|
|
298
313
|
this.cannonConstraint && n.removeConstraint(this.cannonConstraint);
|
|
299
314
|
}
|
|
300
315
|
}
|
|
301
|
-
class
|
|
316
|
+
class N extends b {
|
|
302
317
|
constructor(n, t) {
|
|
303
318
|
super(n), this.body = t;
|
|
304
319
|
}
|
|
@@ -320,7 +335,7 @@ class L extends b {
|
|
|
320
335
|
this.obj.position.copy(o), this.obj.quaternion.copy(e);
|
|
321
336
|
}
|
|
322
337
|
}
|
|
323
|
-
class
|
|
338
|
+
class I extends b {
|
|
324
339
|
constructor(n, t, e, o, s) {
|
|
325
340
|
super(t), this.world = n, this.cable = e, o && this.lockHeadTo(o), s && this.lockTailTo(s);
|
|
326
341
|
}
|
|
@@ -328,7 +343,7 @@ class N extends b {
|
|
|
328
343
|
lockToB;
|
|
329
344
|
lockXTo(n, t, e) {
|
|
330
345
|
let o;
|
|
331
|
-
return n && this.world.removeConstraint(n), e && (o = new
|
|
346
|
+
return n && this.world.removeConstraint(n), e && (o = new x(t, new l(), e, e.pointToLocalFrame(t.position)), o.collideConnected = !1, this.world.addConstraint(o)), o;
|
|
332
347
|
}
|
|
333
348
|
/**
|
|
334
349
|
* Locks or Releases the head to or from a body. `PointToPointConstraint` will be used to lock.
|
|
@@ -358,8 +373,8 @@ class N extends b {
|
|
|
358
373
|
}
|
|
359
374
|
}
|
|
360
375
|
export {
|
|
361
|
-
|
|
362
|
-
|
|
376
|
+
I as CableConstraint,
|
|
377
|
+
N as SyncConstraint,
|
|
363
378
|
b as ThreeJsCannonEsConstraint,
|
|
364
|
-
|
|
379
|
+
R as ThreeJsCannonEsSceneRigger
|
|
365
380
|
};
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"url": "https://github.com/bandinopla"
|
|
6
6
|
},
|
|
7
7
|
"description": "Create and position physics colliders in Blender then export them as GLB and load them automatically in ThreeJs with Cannon-es.",
|
|
8
|
-
"version": "1.0.
|
|
8
|
+
"version": "1.0.4",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"packageManager": "pnpm@8.15.4",
|
|
11
11
|
"files": [
|
package/readme.md
CHANGED
|
@@ -5,12 +5,27 @@
|
|
|
5
5
|
|
|
6
6
|

|
|
7
7
|
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
- [Who is this for?](#who-is-this-for)
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [Test your rig](#test-your-rig)
|
|
13
|
+
- [Usage](#usage)
|
|
14
|
+
- [API](#api)
|
|
15
|
+
- [Colliders / Bodies](#colliders--bodies)
|
|
16
|
+
- [Constraints](#constraints)
|
|
17
|
+
- [Relationship Lines](#relationship-lines)
|
|
18
|
+
- [Collision groups and masks](#collision-groups-and-masks)
|
|
19
|
+
- [Custom constaint](#sparkles-custom-constaint)
|
|
20
|
+
- [License](#license)
|
|
21
|
+
|
|
8
22
|
# ThreeJs / Cannon-es Scene Rigger
|
|
9
23
|
### Design in Blender, simulate in Three!
|
|
10
24
|
|
|
11
25
|
Create and place physics colliders in [Blender](http://blender.org/), export as GLB, and automatically set them up in [three.js](https://threejs.org/) with [cannon-es](https://github.com/pmndrs/cannon-es).
|
|
12
26
|
|
|
13
|
-
Watch
|
|
27
|
+
Watch [Video Tutorial (Rigging a mechanical claw)](https://youtu.be/RtO2KUH9Vig)
|
|
28
|
+
Demo showcase app that inspired this addon: [Mechanical Claw Machine](https://threejs-claw-machine.vercel.app/)
|
|
14
29
|
|
|
15
30
|
This solution includes two tools:
|
|
16
31
|
|
|
@@ -18,6 +33,9 @@ This solution includes two tools:
|
|
|
18
33
|
2) An NPM package to rig the physics in Three.js using cannon-es
|
|
19
34
|
|
|
20
35
|
|
|
36
|
+
## Who is this for?
|
|
37
|
+
Manually building complex physical rigs vía code can be a headache in the making. This toolset aims to make it a smooth visual experience. Focus on the design aspect in Blender, and let the devs work on the details once the rig is exported. Separate concerns.
|
|
38
|
+
|
|
21
39
|
## Features
|
|
22
40
|
|
|
23
41
|
- Automatically creates Cannon bodies for Three.js objects defined inside of Blender ( _using the addon_ ).
|
|
@@ -39,8 +57,11 @@ Just make sure your glb has a camera and it is in the right angle where you want
|
|
|
39
57
|
## Usage
|
|
40
58
|
|
|
41
59
|
### 1) Install the blender addon
|
|
42
|
-
|
|
43
|
-
|
|
60
|
+
Install from disk → [threejs-cannones-addon.py](https://github.com/bandinopla/threejs-cannones-rigger/raw/refs/heads/main/threejs-cannones-addon.py)
|
|
61
|
+
|
|
62
|
+
| Blender → Preferences | Add-ons → Install (.py) | New Object Panel |
|
|
63
|
+
|---------|---------|---------|
|
|
64
|
+
|  |  |  |
|
|
44
65
|
|
|
45
66
|
After installing, when you select an object in the scene inside of blender, you should see new expandable box appear in the Object's tab.
|
|
46
67
|
|
|
@@ -102,8 +123,8 @@ constructor(world: World, scene?: Object3D)
|
|
|
102
123
|
- `getBodyByName(name: string)`: Returns a Cannon body by the name (the name in `userData.name` )
|
|
103
124
|
|
|
104
125
|
---
|
|
105
|
-
#
|
|
106
|
-
|
|
126
|
+
# Colliders / Bodies
|
|
127
|
+

|
|
107
128
|
|
|
108
129
|
#### Box / Sphere Collider
|
|
109
130
|
> Use a default Cube or UV Sphere. Scale and rotate as needed. Only spheres must be scaled uniformly; boxes can be stretched freely.
|
|
@@ -112,7 +133,22 @@ rigger.getBodyByName(name) //-> CANNON.Body
|
|
|
112
133
|
```
|
|
113
134
|
|
|
114
135
|
#### Compound Collider
|
|
115
|
-
|
|
136
|
+
Assign this to an empty. All children will be glued into one collider/Body. The children **should be empty boxes** with your desire scale, rotation and positioning.
|
|
137
|
+
|
|
138
|
+
#### Tube / Cable
|
|
139
|
+
>Creates a flexible cable using [threejs-cannones-tube](https://www.npmjs.com/package/threejs-cannones-tube).
|
|
140
|
+
Add two child empties to the constraint object — one for the head, one for the tail. A and B can optionally anchor the ends.
|
|
141
|
+
|
|
142
|
+
**Material** : If the constraint body is a mesh (like a Box) it will use whatever material that mesh has and assign it to the mesh of the tube.
|
|
143
|
+
```js
|
|
144
|
+
rigger.getCableConstraint(name) //-> CableConstraint
|
|
145
|
+
rigger.getCableConstraint(name).cable //-> CannonTubeRig
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
# Constraints
|
|
149
|
+

|
|
150
|
+
|
|
151
|
+
In all cases, when you call `get___Constraint( name )` the expected name is the name of the object as you read it in blender. Which is automatically put in `userData.name` when you export to glb.
|
|
116
152
|
|
|
117
153
|
#### Glue/Lock Colliders
|
|
118
154
|
> Connect two colliders (A & B) so they behave as a single rigid body. Creates a LockConstraint...
|
|
@@ -142,17 +178,27 @@ rigger.getDistanceConstraint(name) //-> CANNON.DistanceConstraint
|
|
|
142
178
|
> Use this on a visible object (e.g. mesh) to match the position & rotation of a physics collider.
|
|
143
179
|
```js
|
|
144
180
|
rigger.getSyncConstraint(name) //-> SyncConstraint
|
|
145
|
-
```
|
|
181
|
+
```
|
|
146
182
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
183
|
+
# Relationship Lines
|
|
184
|
+

|
|
185
|
+
When a contraints references other objects, lines will appear to easily see their relationships with the currently selected object. You can toogle thouse lines on and off using the "Show Overlays" button.
|
|
150
186
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
187
|
+
# Collision groups and masks
|
|
188
|
+
The panel will show two key properties for physics-based collision management when you select a collider's body type. You can select 1 or many (clicking and holding SHIFT)
|
|
189
|
+
>
|
|
190
|
+
|
|
191
|
+

|
|
192
|
+
|
|
193
|
+
- **Collision Group `collisionGroup`**: "I'm in group...". Bitmask. Defines which group(s) the selected object belongs to. This is a single group index (0–31) that identifies the object's collision category.
|
|
194
|
+
- **Collision Mask `collisionMask`**: "I collide with...".Bitmask. Specifies which group(s) the object collides with, using a 32-bit boolean array (checkboxes). Each checkbox corresponds to one of the 32 possible collision groups.
|
|
195
|
+
|
|
196
|
+
### Understanding Collision Group and Mask
|
|
197
|
+
- **Collision Group**: Sets which group your object belongs to (0–31). Think of it as the object’s "team" in the physics simulation.
|
|
198
|
+
- **Collision Mask**: Controls which groups your object can collide with. Check boxes (1–32) to choose which "teams" it interacts with. For example, checking Group 2 means it collides with objects in Group 2.
|
|
199
|
+
|
|
200
|
+
> #### When Collisions Don’t Happen
|
|
201
|
+
> A collision between two objects won’t occur if their **Collision Masks** don’t include each other’s **Collision Group**. For example, if Object A’s mask doesn’t check Group 2, and Object B is in Group 2, they won’t collide, even if they touch in the physics simulation.
|
|
156
202
|
|
|
157
203
|
# :sparkles: Custom constaint
|
|
158
204
|
In blender you can select "**Custom Constraint**" and pass a custom id (an arbitraty string of your choosing) then in javascript side, you define it like so:
|