Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scaled GImpactShapes fall through MeshCollisionShape #1120

Open
louhy opened this issue Jun 12, 2019 · 13 comments

Comments

@louhy
Copy link
Contributor

commented Jun 12, 2019

This issue was discovered while putting together the following test draft. Behavior can be reproduced in JBullet only (tests with native bullet work fine).

package jme3test.bullet.shape;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.GImpactCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.debug.BulletDebugAppState;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import com.jme3.scene.shape.Sphere;
import java.util.ArrayList;
import java.util.List;
import jme3test.bullet.PhysicsTestHelper;

/**
 * 1st Test: 10 solver iterations, large pot<br>
 * 2nd Test: 20 solver iterations, large pot<br>
 * 3rd Test: 30 solver iterations, large pot<br>
 * 4th Test: 10 solver iterations, small pot<br>
 * 5th Test: 20 solver iterations, small pot<br>
 * 6th Test: 30 solver iterations, small pot
 *
 * @author lou
 */
public class TestGimpactShape extends SimpleApplication {

    private BulletAppState bulletAppState;
    private final boolean physicsDebug = true;
    private int solverNumIterations = 10;
    protected BitmapFont font;
    protected BitmapText timeElapsedTxt;
    protected BitmapText solverNumIterationsTxt;
    private final List<Spatial> testObjects = new ArrayList<>();
    private float testTimer = 0;
    private final float TIME_PER_TEST = 10;
    private float teapotScale = 1;

    public static void main(String[] args) {
        TestGimpactShape a = new TestGimpactShape();
        a.start();
    }

    @Override
    public void simpleInitApp() {
        getCamera().setLocation(new Vector3f(0, 10, 25));
        getCamera().lookAt(new Vector3f(0, -5, 0), Vector3f.UNIT_Y);
        getFlyByCamera().setMoveSpeed(25);

        DirectionalLight dl = new DirectionalLight();
        dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal());
        dl.setColor(ColorRGBA.Green);
        rootNode.addLight(dl);

        guiNode = getGuiNode();
        font = assetManager.loadFont("Interface/Fonts/Default.fnt");
        timeElapsedTxt = new BitmapText(font, false);
        solverNumIterationsTxt = new BitmapText(font, false);
        float lineHeight = timeElapsedTxt.getLineHeight();

        timeElapsedTxt.setLocalTranslation(202, lineHeight * 1, 0);
        guiNode.attachChild(timeElapsedTxt);
        solverNumIterationsTxt.setLocalTranslation(202, lineHeight * 2, 0);
        guiNode.attachChild(solverNumIterationsTxt);

        init();
    }

    private void init() {
        solverNumIterationsTxt.setText("Solver Iterations: " + solverNumIterations);

        bulletAppState = new BulletAppState();
        bulletAppState.setDebugEnabled(physicsDebug);
        stateManager.attach(bulletAppState);
        bulletAppState.getPhysicsSpace().setSolverNumIterations(solverNumIterations);

        //Left side test - GImpact objects collide with MeshCollisionShape floor
        dropTest(-5, 2, 0);
        dropTest2(-11, 7, 3);

        Geometry leftFloor = PhysicsTestHelper.createMeshTestFloor(assetManager, 20, new Vector3f(-21, -5, -10));
        addObject(leftFloor);

        //Right side test - GImpact objects collide with GImpact floor
        dropTest(10, 2, 0);
        dropTest2(9, 7, 3);

        Geometry rightFloor = PhysicsTestHelper.createGImpactTestFloor(assetManager, 20, new Vector3f(0, -5, -10));
        addObject(rightFloor);

        //Hide physics debug visualization for floors
        if (physicsDebug) {
            BulletDebugAppState bulletDebugAppState = stateManager.getState(BulletDebugAppState.class);
            bulletDebugAppState.setFilter((Object obj) -> {
                return !(obj.equals(rightFloor.getControl(RigidBodyControl.class))
                    || obj.equals(leftFloor.getControl(RigidBodyControl.class)));
            });
        }
    }

    private void addObject(Spatial s) {
        testObjects.add(s);
        rootNode.attachChild(s);
        physicsSpace().add(s);
    }

    private void dropTest(float x, float y, float z) {
        Vector3f offset = new Vector3f(x, y, z);
        attachTestObject(new Sphere(16, 16, 0.5f), new Vector3f(-4f, 2f, 2f).add(offset), 1);
        attachTestObject(new Sphere(16, 16, 0.5f), new Vector3f(-5f, 2f, 0f).add(offset), 1);
        attachTestObject(new Sphere(16, 16, 0.5f), new Vector3f(-6f, 2f, -2f).add(offset), 1);
        attachTestObject(new Box(0.5f, 0.5f, 0.5f), new Vector3f(-8f, 2f, -1f).add(offset), 10);
        attachTestObject(new Box(0.5f, 0.5f, 0.5f), new Vector3f(0f, 2f, -6f).add(offset), 10);
        attachTestObject(new Box(0.5f, 0.5f, 0.5f), new Vector3f(0f, 2f, -3f).add(offset), 10);
        attachTestObject(new Cylinder(2, 16, 0.2f, 2f), new Vector3f(0f, 2f, -5f).add(offset), 2);
        attachTestObject(new Cylinder(2, 16, 0.2f, 2f), new Vector3f(-1f, 2f, -5f).add(offset), 2);
        attachTestObject(new Cylinder(2, 16, 0.2f, 2f), new Vector3f(-2f, 2f, -5f).add(offset), 2);
        attachTestObject(new Cylinder(2, 16, 0.2f, 2f), new Vector3f(-3f, 2f, -5f).add(offset), 2);
    }

    private void dropTest2(float x, float y, float z) {
        Node n = (Node) assetManager.loadModel("Models/Teapot/Teapot.mesh.xml");
        n.setLocalTranslation(x, y, z);
        n.rotate(0, 0, -FastMath.HALF_PI);
        n.scale(teapotScale);

        Geometry tp = ((Geometry) n.getChild(0));
        Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
        tp.setMaterial(mat);

        Mesh mesh = tp.getMesh();
        GImpactCollisionShape shape = new GImpactCollisionShape(mesh);
        shape.setScale(new Vector3f(teapotScale, teapotScale, teapotScale));

        RigidBodyControl control = new RigidBodyControl(shape, 2);
        n.addControl(control);
        addObject(n);
    }

    private void attachTestObject(Mesh mesh, Vector3f position, float mass) {
        Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
        Geometry g = new Geometry("mesh", mesh);
        g.setLocalTranslation(position);
        g.setMaterial(material);

        RigidBodyControl control = new RigidBodyControl(new GImpactCollisionShape(mesh), mass);
        g.addControl(control);
        addObject(g);
    }

    private PhysicsSpace physicsSpace() {
        return bulletAppState.getPhysicsSpace();
    }

    @Override
    public void simpleUpdate(float tpf) {
        testTimer += tpf;

        if (testTimer / TIME_PER_TEST > 1) {
            testTimer = 0;
            switch (solverNumIterations) {
                case 10:
                    solverNumIterations = 20;
                    cleanup();
                    init();
                    break;
                case 20:
                    solverNumIterations = 30;
                    cleanup();
                    init();
                    break;
                case 30:
                    solverNumIterations = 10;
                    teapotScale = teapotScale > 0.9f ? 0.5f : 1;
                    cleanup();
                    init();
                    break;
            }
        }
        timeElapsedTxt.setText("Time Elapsed: " + testTimer);
    }

    private void cleanup() {
        stateManager.detach(bulletAppState);
        stateManager.detach(stateManager.getState(BulletDebugAppState.class));
        for (Spatial s : testObjects) {
            rootNode.detachChild(s);
        }
    }
}

@stephengold

This comment has been minimized.

Copy link
Contributor

commented Jun 12, 2019

Thank you for documenting this issue.

@louhy

This comment has been minimized.

Copy link
Contributor Author

commented Jun 12, 2019

No problem. It does bother me a little that the issue goes away when solverNumIterations is increased high enough. It makes me wonder whether it's not technically a bug and more a matter of native being a slightly more accurate simulation (I can't imagine why that would be).

@stephengold

This comment has been minimized.

Copy link
Contributor

commented Jun 20, 2019

I believe that invoking updateBound() during setScale() will fix this, for native Bullet at least.

@stephengold

This comment has been minimized.

Copy link
Contributor

commented Jun 20, 2019

Looks like jme3-jbullet's GImpactCollisionShape.java has an update issue similar to that in jme3-bullet:

        cShape = new GImpactMeshShape(tiv);
        cShape.setLocalScaling(Converter.convert(worldScale));
        ((GImpactMeshShape)cShape).updateBound();
        cShape.setLocalScaling(Converter.convert(getScale()));
        cShape.setMargin(margin);

I'll try moving the updateBound() into setScale() in jme3-jbullet.

stephengold pushed a commit that referenced this issue Jun 20, 2019

@stephengold

This comment has been minimized.

Copy link
Contributor

commented Jun 20, 2019

@louhy please confirm the fix is good. Also, check whether there's a similar issue with GImpactCollisionShape.setMargin().

@stephengold stephengold changed the title JBullet GImpactShapes fall through MeshCollisionShape scaled GImpactShapes fall through MeshCollisionShape Jun 20, 2019

@louhy

This comment has been minimized.

Copy link
Contributor Author

commented Jun 21, 2019

Nice find! Will double check this fix for you. I think we should make a separate dedicated test for it too (using the code above or a variant), ex "TestIssue1120.java". If there's any way to add me as a second assignee here so I don't forget, feel free.

@stephengold

This comment has been minimized.

Copy link
Contributor

commented Jun 21, 2019

GitHub allows me to assign up to 10 people to an issue. You're in!

@louhy

This comment has been minimized.

Copy link
Contributor Author

commented Jun 23, 2019

I think I have bad news, good news, great news:

Bad: One problem with adding a test based on code above is that it's using GImpact shapes for some primitives where technically you wouldn't do that in practice (your setting a good example suggestion in the other issue). It may be hard to replace those with other shapes while still reproducing a fall through. For JBullet those cylinders still fall through the mesh. Native is fine, but I don't believe this particular test showed an issue for native. But it's a 0.2 radius GImpact cylinder... do we really care?

Good: The current GImpactShape test demonstrates fall-through (and crazy bounces) with the kettle when scale is increased to 1.8.

Great: When I run the current GImpactShape test against the current master version, not only is this fixed, but JBullet performs WAY better and the crazy bounciness seems totally gone (from testing so far). JBullet actually passes the test criteria now if you're patient waiting for things to go inactive.

I think this restores my faith in JBullet's usefulness...

@stephengold

This comment has been minimized.

Copy link
Contributor

commented Jun 24, 2019

If 0.2-radius GImpact cylinder falls through in jme3-jbullet, I think that's an issue. Not a high-priority issue for me, but it's an issue.

So can we close this issue (for the latest master branch, at least)?

@louhy

This comment has been minimized.

Copy link
Contributor Author

commented Jun 24, 2019

Since this looks way better in master now, I'm fine with considering it closed.

I'd be happy to turn the code here into a slimmed down test PR which demonstrates the GImpact cylinder fall through if you'd like.

@stephengold

This comment has been minimized.

Copy link
Contributor

commented Jun 24, 2019

I'd like to play with that slimmed-down test case.

@stephengold stephengold added this to the v3.2.4 milestone Jun 26, 2019

@louhy

This comment has been minimized.

Copy link
Contributor Author

commented Jul 5, 2019

Hmm, maybe we shouldn't have closed this since the OP is specifically for the JBullet fall-through issue (which still remains). Anyway, I've got a smaller test case eliminating the distractions, and fairly minimal code.

Running this one at half speed by default since it moves kind of fast. I've also made the speed variable. (Don't forget to switch to JBullet before testing.) PR coming next...

louhy added a commit to louhy/jmonkeyengine that referenced this issue Jul 5, 2019

@stephengold

This comment has been minimized.

Copy link
Contributor

commented Jul 5, 2019

Re-opening as suggested.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
You can’t perform that action at this time.