Skip to content

Commit

Permalink
Merge pull request #156 from chnicoloso/frictionGravity
Browse files Browse the repository at this point in the history
Fix: Friction disappears when manually applying gravity schteppe#224
  • Loading branch information
marcofugaro authored Aug 12, 2022
2 parents 5b51c3a + 4f7cb4d commit 1d3d09c
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 1 deletion.
1 change: 1 addition & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ These minor changes and improvements were also made:
- Add support for [Trigger bodies](https://pmndrs.github.io/cannon-es/examples/trigger). [#83](https://github.com/pmndrs/cannon-es/pull/83)
- Deprecated properties and methods have been removed.
- The [original cannon.js debugger](https://github.com/schteppe/cannon.js/blob/master/tools/threejs/CannonDebugRenderer.js), which shows the wireframes of each body, has been moved to its own repo [cannon-es-debugger](https://github.com/pmndrs/cannon-es-debugger).
- Added optional property `World.frictionGravity: Vec3` which can be set to customize the force used when computing the friction between two colliding bodies. If `undefined`, `World.gravity` will be used. This property is useful to enable friction in zero gravity. This addresses issue [#224](https://github.com/schteppe/cannon.js/issues/224) and follows the [pattern established for p2.js](https://github.com/schteppe/p2.js/blob/master/src/world/World.js#L88-L92).

If instead you're using three.js in a **React** environment with [react-three-fiber](https://github.com/pmndrs/react-three-fiber), check out [use-cannon](https://github.com/pmndrs/use-cannon)! It's a wrapper around cannon-es.

Expand Down
87 changes: 87 additions & 0 deletions src/world/Narrowphase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Sphere } from '../shapes/Sphere'
import { Body } from '../objects/Body'
import { World } from '../world/World'
import { ContactEquation } from '../equations/ContactEquation'
import { FrictionEquation } from '../equations/FrictionEquation'

describe('Narrowphase', () => {
test('sphere + sphere contact', () => {
Expand Down Expand Up @@ -63,6 +64,92 @@ describe('Narrowphase', () => {

expect(result.length).toBe(1)
})

test('should default to using global gravity to create friction equations', () => {
const gravity = new Vec3(0, -9.81, 0)
const world = new World({ gravity })
// No frictionGravity override.
expect(world.frictionGravity).toBeUndefined()

const narrowPhase = new Narrowphase(world)
const contacts: ContactEquation[] = []
const sphereShape = new Sphere(1)

const bodyA = new Body({ mass: 1 })
const bodyB = new Body({ mass: 1 })
bodyA.addShape(sphereShape)
bodyB.addShape(sphereShape)

narrowPhase.result = contacts
narrowPhase.sphereSphere(
sphereShape,
sphereShape,
new Vec3(0.5, 0, 0),
new Vec3(-0.5, 0, 0),
new Quaternion(),
new Quaternion(),
bodyA,
bodyB
)

expect(contacts.length).toBe(1)
const [contact] = contacts
const result: FrictionEquation[] = []

// No frictionGravity, should use global gravity.
narrowPhase.createFrictionEquationsFromContact(contact, result)
expect(result.length).toBe(2)
result.forEach((result) => {
expect(result.maxForce > 0).toBe(true)
})
})

test('if provided, should use frictionGravity to create friction equations', () => {
const gravity = new Vec3(0, 0, 0)
const frictionGravity = new Vec3(0, -9.81, 0)
const world = new World({ gravity, frictionGravity })

const narrowPhase = new Narrowphase(world)
const contacts: ContactEquation[] = []
const sphereShape = new Sphere(1)

const bodyA = new Body({ mass: 1 })
const bodyB = new Body({ mass: 1 })
bodyA.addShape(sphereShape)
bodyB.addShape(sphereShape)

narrowPhase.result = contacts
narrowPhase.sphereSphere(
sphereShape,
sphereShape,
new Vec3(0.5, 0, 0),
new Vec3(-0.5, 0, 0),
new Quaternion(),
new Quaternion(),
bodyA,
bodyB
)

expect(contacts.length).toBe(1)
const [contact] = contacts
const result: FrictionEquation[] = []

// No gravity, frictionGravity defined, friction.
narrowPhase.createFrictionEquationsFromContact(contact, result)
expect(result.length).toBe(2)
result.forEach((result) => {
expect(result.maxForce > 0).toBe(true)
})

// No gravity, no frictionGravity, no friction.
result.length = 0
world.frictionGravity = new Vec3(0, 0, 0)
narrowPhase.createFrictionEquationsFromContact(contact, result)
expect(result.length).toBe(2)
result.forEach((result) => {
expect(result.maxForce).toBe(0)
})
})
})

function createHeightfield() {
Expand Down
3 changes: 2 additions & 1 deletion src/world/Narrowphase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ export class Narrowphase {

if (friction > 0) {
// Create 2 tangent equations
const mug = friction * world.gravity.length()
// Users may provide a force different from global gravity to use when computing contact friction.
const mug = friction * (world.frictionGravity || world.gravity).length()
let reducedMass = bodyA.invMass + bodyB.invMass
if (reducedMass > 0) {
reducedMass = 1 / reducedMass
Expand Down
17 changes: 17 additions & 0 deletions src/world/World.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,21 @@ describe('World', () => {
test('using ObjectCollisionMatrix', () => {
testCollisionMatrix(ObjectCollisionMatrix)
})

test('frictionGravity: should be undefined by default', () => {
const gravity = new Vec3(0, -9.81, 0)
const world = new World({ gravity })

expect(world.gravity).toEqual(gravity)
expect(world.frictionGravity).toBeUndefined()
})

test('frictionGravity: should be configurable', () => {
const gravity = new Vec3(0, 0, 0)
const frictionGravity = new Vec3(0, -9.81, 0)
const world = new World({ gravity, frictionGravity })

expect(world.gravity).toEqual(gravity)
expect(world.frictionGravity).toEqual(frictionGravity)
})
})
16 changes: 16 additions & 0 deletions src/world/World.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ export class World extends EventTarget {
*/
gravity: Vec3

/**
* Gravity to use when approximating the friction max force (mu*mass*gravity).
* If undefined, global gravity will be used.
* Use to enable friction in a World with a null gravity vector (no gravity).
*/
frictionGravity?: Vec3

/**
* The broadphase algorithm to use.
* @default NaiveBroadphase
Expand Down Expand Up @@ -170,6 +177,11 @@ export class World extends EventTarget {
* The gravity of the world.
*/
gravity?: Vec3
/**
* Gravity to use when approximating the friction max force (mu*mass*gravity).
* If undefined, global gravity will be used.
*/
frictionGravity?: Vec3
/**
* Makes bodies go to sleep when they've been inactive.
* @default false
Expand Down Expand Up @@ -215,6 +227,10 @@ export class World extends EventTarget {
if (options.gravity) {
this.gravity.copy(options.gravity)
}
if (options.frictionGravity) {
this.frictionGravity = new Vec3()
this.frictionGravity.copy(options.frictionGravity)
}

this.broadphase = options.broadphase !== undefined ? options.broadphase : new NaiveBroadphase()
this.bodies = []
Expand Down

0 comments on commit 1d3d09c

Please sign in to comment.