Missing Collisions in Collision-Events

Hello jme-community,

I have an problem with the collision detection and I hope that someone on here can help me.

What worked so far: I have a loaded Map, and Player(s). Collision detection function as expected. Player(s) doesn’t fall or glitches through map - nice.

The Problem: My latest features adds items to the game. The Player can run against them, the Item will stop the player (item has a mass of 0), so I suspect the collision is working, But my CollisionListener doesn’t receive any collision events for this.

My expections: I added the items exactly the way I added the player. Player, Level and Item have both the same collision-group. The collision can be experienced by the player. I expected to now have the collision-events for player and map, and also for player and item. But that isn’t the case.

Currently I’m really not sure what’s wrong. I have an other Project with older jme-version and BulletAppState where something similar works, but didn’t find any differences in the game-code nor jme-code. I tried to find similar problems in the community but didn’t found any. I highly suspect that I’m missing something configuration or method-invocation-wise, but cannot find anything in jme-code. So I’m asking you for help.

Cheers
qriz

The Code (kotlin):
(jme3-3.2.1-stable with jme3-bullet-native, caution I do not use BulletAppState)

How the level is added to PhysicsSpace:

val levelNode = assetManager.loadModel(levelName) as Node  // (level is j3o-file)
val levelShape = CollisionShapeFactory.createMeshShape(levelNode)
val levelControl = RigidBodyControl(levelShape, 0f)
levelControl.userObject = GameObject.INVALID_ID //kotlin invokes setUserObject

physicSpace.add(loadedLevel.physicLevel)

How the player is added to PhysicsSpace:

val playerShape = SphereCollisionShape(1f)
val playerControl = RigidBodyControl(playerShape, 0.1f)
playerControl.userObject = playerId

physicSpace.add(playerControl)

The player position is updated before each bullet invocation:

playerControl.physicsLocation = (player.playerState.position) //kotlin invokes setPhysicsLocation
playerControl.physicsRotation = Quaternion.IDENTITY

How the item is added to PhysicsSpace:

val itemShape = SphereCollisionShape(0.5f)
val itemControl = RigidBodyControl(itemShape, 0f)

itemControl.userObject = itemId
itemControl.physicsLocation = state.position
itemControl.physicsRotation = Quaternion.IDENTITY

physicSpace.add(itemControl)

How PhysicsSpace is invoked:

physicSpace.update(delta.toFloat() / 1000f)
physicSpace.distributeEvents()

How PhysicsCollisionListener is added:

physicSpace.addCollisionListener(this)

Are you adding your PhysicsCollisionListener to physic space ?

Take a look at the Physics Listeners tutorial

https://jmonkeyengine.github.io/wiki/jme3/advanced/physics_listeners.html

I would first check that they are infact intersecting by using the debug mode on the bulletappstate and ensuring the collision meshes are where they should be. Note that you mentioned you don’t use bulletappstate but didn’t explain why. It may have no use in the answer but the question remains. Are you using double precision for tpf maybe? I guess?

I guess good old logging could give you a hand on that part.

Thank you, for your answer and thoughts. I start working on a minimal jme-game to showcase my problem, when the program works I know that I messed up, somewhere in my code… :slight_smile:

@Ali_RS
I indeed added the PhysicsCollisionListener (I’m going to edit my original post). Thanks for the reference I reread it.

@jayfella
If I understand you correctly you want to make sure that bullet-physics has indeed the objects. I do not have the convenience of an debug mode, but if the object wouldn’t be there the player couldn’t experience the collision. So I assume that this is at least correct.

Why I didn’t use BulletAppState? Well it is hard to explain without giving you an indepth explanation. In short my network-logic needs to invoke the physic-logic multiple-times in one update, therefore the separation seemed the right thing to do.

I’m not sure what you mean with double-precision in tpf. jme provides me with an float in AbstractAppState::update and PhysicsSpace::update expects an float, so I see no issue here. In my “Framework” however I use an Integer for the milliseconds. To be honest, I didn’t expect it to be an problem, but you are right that it could be an problem - that really makes me think, thank you.

Hello jme-community,

I just completed the minimal game, which has the exact same issue (Press W or S to ram the yellow-item). It’s written in Kotlin if that’s an problem, I can change it to Java.

Help is really appreciated.

Cheers
qriz

import com.jme3.app.SimpleApplication
import com.jme3.bullet.PhysicsSpace
import com.jme3.bullet.collision.PhysicsCollisionEvent
import com.jme3.bullet.collision.PhysicsCollisionListener
import com.jme3.bullet.collision.shapes.CollisionShape
import com.jme3.bullet.collision.shapes.SphereCollisionShape
import com.jme3.bullet.control.RigidBodyControl
import com.jme3.bullet.util.CollisionShapeFactory
import com.jme3.input.KeyInput
import com.jme3.input.controls.ActionListener
import com.jme3.input.controls.KeyTrigger
import com.jme3.material.Material
import com.jme3.math.ColorRGBA
import com.jme3.math.Vector3f
import com.jme3.scene.Geometry
import com.jme3.scene.shape.Box

class TestFps : SimpleApplication(), ActionListener, PhysicsCollisionListener {

    companion object {
        val LEVEL_ID = -1
        val PLAYER_ID = 23
        val ITEM_ID = 42

        val FORWARD_ACTION = "forward"
        val BACKWARD_ACTION = "backward"

        val FORWARD_VELOCITY = Vector3f(0.0f, 0.0f, -8.0f)
        val BACKWARD_VELOCITY = Vector3f(0.0f, 0.0f, 8.0f)
    }

    // PHYSICS
    private lateinit var physicsSpace: PhysicsSpace

    private lateinit var levelShape: CollisionShape
    private lateinit var levelControl: RigidBodyControl

    private lateinit var playerShape: CollisionShape
    private lateinit var playerControl: RigidBodyControl

    private lateinit var itemShape: CollisionShape
    private lateinit var itemControl: RigidBodyControl

    // RENDERING
    private lateinit var levelGeometry: Geometry

    private lateinit var playerGeometry: Geometry

    private lateinit var itemGeometry: Geometry

    private var initDone = false

    // INPUT
    private var forward = false

    private var backward = false

    // GAME-STATE
    private val playerPosition = Vector3f(0.0f, 4.0f, 5.0f)
    private val itemPosition = Vector3f(0.0f, 1.5f, 0.0f)

    override fun simpleInitApp() {
        flyCam.isEnabled = false

        cam.location.set(10.0f, 10.0f, 10.0f)
        cam.lookAt(Vector3f(0.0f, 0.0f, 0.0f), Vector3f(0.0f, 1.0f, 0.0f))

        registerActions()
    }

    private fun registerActions() {
        inputManager.addMapping(FORWARD_ACTION, KeyTrigger(KeyInput.KEY_W))
        inputManager.addMapping(BACKWARD_ACTION, KeyTrigger(KeyInput.KEY_S))

        inputManager.addListener(this, FORWARD_ACTION)
        inputManager.addListener(this, BACKWARD_ACTION)
    }

    override fun onAction(name: String?, isPressed: Boolean, tpf: Float) {
        when (name) {
            FORWARD_ACTION -> {
                forward = isPressed
            }
            BACKWARD_ACTION -> {
                backward = isPressed
            }
        }
    }

    override fun collision(event: PhysicsCollisionEvent?) {
        if (event != null) {
            val idA = event.objectA.userObject as Int
            val idB = event.objectB.userObject as Int

            if((ITEM_ID == idA || ITEM_ID == idB) && (PLAYER_ID == idA || PLAYER_ID == idB)) {
                throw IllegalStateException("A Exception when everything works")
            }

            if((PLAYER_ID == idA || PLAYER_ID == idB) && (LEVEL_ID == idA || LEVEL_ID == idB)) {
                //Comment me in to spam the console.
                //System.out.println("collision works..")
            }
        }
    }

    override fun simpleUpdate(tpf: Float) {
        if (!initDone) {
            //Cannot be done in InitApp, missing native bullet-lib.
            initRendering()
            initPhysics()
            initDone = true
        }

        updatePhysicNodes()
        calculatePhysics(tpf)
        updateGameState()
        updateRenderingNodes()

    }

    private fun updatePhysicNodes() {
        playerControl.physicsLocation = playerPosition
        itemControl.physicsLocation = itemPosition

        if (forward) {
            playerControl.linearVelocity = FORWARD_VELOCITY
        }
        if (backward) {
            playerControl.linearVelocity = BACKWARD_VELOCITY
        }
    }

    private fun calculatePhysics(tpf: Float) {
        physicsSpace.update(tpf)
        physicsSpace.distributeEvents()
    }

    private fun updateGameState() {
        playerPosition.set(playerControl.physicsLocation)
        itemPosition.set(itemControl.physicsLocation)
    }

    private fun updateRenderingNodes() {
        playerGeometry.setLocalTranslation(playerPosition.x, playerPosition.y, playerPosition.z)
        itemGeometry.setLocalTranslation(itemPosition.x, itemPosition.y, itemPosition.z)

        //Make sure that frame is rendered.
        cam.onFrameChange()
    }

    private fun initRendering() {
        val levelMesh = Box(50.0f, 1.0f, 50.0f)
        val levelMaterial = Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")
        levelMaterial.setColor("Color", ColorRGBA.Blue)
        levelGeometry = Geometry("level", levelMesh)
        levelGeometry.material = levelMaterial

        val playerMesh = Box(1f, 1f, 1f)
        val playerMaterial = Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")
        playerMaterial.setColor("Color", ColorRGBA.Red)
        playerGeometry = Geometry("player", playerMesh)
        playerGeometry.material = playerMaterial

        val itemMesh = Box(0.5f, 0.5f, 0.5f)
        val itemMaterial = Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")
        itemMaterial.setColor("Color", ColorRGBA.Yellow)
        itemGeometry = Geometry("item", itemMesh)
        itemGeometry.material = itemMaterial
        itemGeometry.setLocalTranslation(0.0f, 4f, 0.0f)

        rootNode.attachChild(levelGeometry)
        rootNode.attachChild(playerGeometry)
        rootNode.attachChild(itemGeometry)
    }

    private fun initPhysics() {
        physicsSpace = PhysicsSpace()

        levelShape = CollisionShapeFactory.createBoxShape(levelGeometry)
        levelControl = RigidBodyControl(levelShape, 0.0f)
        levelControl.userObject = LEVEL_ID

        playerShape = SphereCollisionShape(1f)
        playerControl = RigidBodyControl(playerShape, 0.1f)
        playerControl.userObject = PLAYER_ID

        itemShape = SphereCollisionShape(0.5f)
        itemControl = RigidBodyControl(itemShape, 0f)
        itemControl.userObject = ITEM_ID

        physicsSpace.add(levelControl)
        physicsSpace.add(playerControl)
        physicsSpace.add(itemControl)

        physicsSpace.addCollisionListener(this)
    }
}

fun main(args: Array<String>) {
    val testFps = TestFps()
    testFps.isShowSettings = false
    testFps.start()
}

Interestingly if you set the player right above the the Item (e.g. Vector3f(0.0f, 5.0f, 0.0f)) the TestFps::collision-Method will not be called. Is that an expected behavior?

I guess I’ll have to read the bullet-documentation and track the native-method call’s…

Another small update: The PhysicsCollisionGroupListener does what I expect. It will be called for both player to level and player to item.

I’m not really satisfied with the “solution”, since it seems that I only avoid the actual problem. Depending on my freetime, I going to check on that issue again…