Basic use of V2 Havok physics joints (BallAndSocketConstraint)

Hello, I’m confused how to get even a basic implementation of a BallAndSocketConstraint working with Havok V2 physics.

I create two mesh cylinders between pointA and pointB, and between pointB and pointC (where pointB is where there should be a connection between two).

For the constructor for BallAndSocketConstraint, there are 4 arguments: pivotA, pivotB, axisA, axisB

For the first two arguments, I have to convert points into local space:

	const localPointB = BB.Vector3.TransformCoordinates(pointB, LINE_A.getWorldMatrix().invert())
	const localPointC = BB.Vector3.TransformCoordinates(pointC, LINE_B.getWorldMatrix().invert())

Now when I create my ball and socket joint, they seem to be connected at pointB, but when the physics run, they fling off in erratic directions and spin:


		const JOINT = new BB.BallAndSocketConstraint(
			localPointB,
			localPointC,
			new BB.Vector3(1,1,1),
			new BB.Vector3(1,1,1),
			scene
		)
		LINE_A.physicsBody.addConstraint(LINE_B.physicsBody, JOINT)

So I suppose I have to do something complicated for axisA and axisB

However already at this stage it seems needlessly weird and complicated by comparison to Ammo, Cannon etc…

Can you please provide a PG ?

2 Likes

Unfortunately a project where the code is intertwined with many things (in this instance the Babylon sketch is abstracted into SvelteJS components).

However … I worked it out!

Some general things that helped were:

  • transforming my worldspace ball and socket coordinates with the world matrix of the mesh its being applied to
  • only getting these mesh world matrixes at initialisation (since this will change between frames)
  • cloning points just in case

Here is an extract (apologies I can’t share full sketch):


	let nodeA = new BB.Vector3(0,1,0)
	let nodeB = new BB.Vector3(-1,0.2,0)

	let outerA1 = new BB.Vector3(-1,0.2,0)
	let outerA2 = new BB.Vector3(1,2,1)
	let outerA3 = new BB.Vector3(-1,2,1)

	let lineA = null // becomes a cylinder mesh between nodeA and outerA1 (and nodeB)
	let lineB = null // becomes a cylinder mesh between nodeA and outerA2 
	let lineC = null // becomes a cylinder mesh between nodeA and outerA3 

	let nodeSphereA = null // becomes sphere mesh at point nodeA
	let nodeSphereB = null // becomes sphere mesh at point nodeB

	let physics, scene, ground

	let lineMatrixA = null // SET THESE ONCE (just in case - YES ✅)
	let lineMatrixB = null // SET THESE ONCE (just in case - YES ✅)
	let lineMatrixC = null // SET THESE ONCE (just in case - YES ✅)

	function createTripleSocket( mesh ) {

		const aggre = new BB.PhysicsAggregate( mesh, BB.PhysicsShapeType.SPHERE, {
			mass: settings.ballMass,
		}, scene )

		// EDGES DON'T COLLIDE (IMPORTANT)

		aggre.shape.filterCollideMask = 1
		aggre.shape.filterMembershipMask = 2

		const centerPivot = new BB.Vector3(0,0,0)

		lineMatrixA = lineA.getWorldMatrix().invert()
		lineMatrixB = lineB.getWorldMatrix().invert()
		lineMatrixC = lineC.getWorldMatrix().invert()

		// GET NODE A POSITION FROM CYLINDER COORDINATES

		const connectedPivotA = BB.Vector3.TransformCoordinates(nodeA.clone(), lineMatrixA)
		const connectedPivotB = BB.Vector3.TransformCoordinates(nodeA.clone(), lineMatrixB)
		const connectedPivotC = BB.Vector3.TransformCoordinates(nodeA.clone(), lineMatrixC)


		// CREATE BALL AND SOCKETS

		const axisA = new BB.Vector3(1, 1, 1)
		const axisB = new BB.Vector3(1, 1, 1)

		const jointA = new BB.BallAndSocketConstraint(
			centerPivot,
			connectedPivotA,
			axisA,
			axisB,
			scene
		)
		const jointB = new BB.BallAndSocketConstraint(
			centerPivot,
			connectedPivotB,
			axisA,
			axisB,
			scene
		)
		const jointC = new BB.BallAndSocketConstraint(
			centerPivot,
			connectedPivotC,
			axisA,
			axisB,
			scene
		)

		mesh.physicsBody.addConstraint(lineA.physicsBody, jointA)
		mesh.physicsBody.addConstraint(lineB.physicsBody, jointB)
		mesh.physicsBody.addConstraint(lineC.physicsBody, jointC)

		addDragBehaviour( mesh )
	}

	function createNextSocket(mesh) {

		const aggre = new BB.PhysicsAggregate( mesh, BB.PhysicsShapeType.SPHERE, {
			mass: settings.ballMass,
		}, scene )

		// EDGES DON'T COLLIDE (IMPORTANT)

		aggre.shape.filterCollideMask = 1
		aggre.shape.filterMembershipMask = 2

		const centerPivot = new BB.Vector3(0,0,0)

		// GET NODE A POSITION FROM CYLINDER COORDINATES

		let POINT = outerA1.clone()
		const connectedPivotA = BB.Vector3.TransformCoordinates(POINT, lineMatrixA) // HELP!


		const axisA = new BB.Vector3(1, 1, 1)
		const axisB = new BB.Vector3(1, 1, 1)

		const jointA = new BB.BallAndSocketConstraint(
			centerPivot,
			connectedPivotA, // HELP! 
			axisA,
			axisB,
			scene
		)

		mesh.physicsBody.addConstraint(lineA.physicsBody, jointA)

		addDragBehaviour( mesh )
	}
1 Like

Simpler version - init assumes that pivot is in world space (and not relative to either mesh):


	function getMeshPivot( pivot, mesh ) {
		const matrix = mesh.getWorldMatrix().clone().invert()
		const transformedPivot = BB.Vector3.TransformCoordinates(pivot.clone(), matrix)
		return transformedPivot
	}

	function init( pivot, meshA, meshB ) {
		constraint = new BB.BallAndSocketConstraint(
			getMeshPivot(pivot, meshA),
			getMeshPivot(pivot, meshB),
			new BB.Vector3(1,1,1),
			new BB.Vector3(1,1,1),
			scene
		)
		meshA.physicsBody.addConstraint(meshB.physicsBody, constraint)
	}
1 Like