Building a physics ship

Not really I made it up…

I made this work before but I was not using the better character control or the JME3 physics.

I had a node inside the ship that would move around when WSAD controls where hit. The camera would snap to that node. Then, after many hours of frustration I figured out how to make the ship rotate with a delta between the camera direction and the ship direction.

The “WarpTo” basicly means that I reposition the player every frame manually. I used custom code for detecting where the walls where. All this was before I even used the JME3 collisions.

My game greatly improved since I started using all the integrated features and now a few things don’t work anymore but I know I will be better off.

Thanks for all the ideas I am getting very close now

Yeah, my player goes through wall and floors as soon as the ship start moving. How did you get around that?
Edit: I added an extra bullet app for the ship interior

… then I’m not sleeping ever again… EVER ZZZzzzzz

Most likely the player is not in the correct physicspace

Hello!

I did allot more testing,

I have each block of the ship as a kinematic. The ship has its own physics space.

I add a non kinematic box onto the ship and set the applyLocalPhysics to true.

I translate the ship along the x axis every frame (slowly) with spatial.setLocalTranslation. (The physics move with the ship)

The box that I put on the ship does not move with the ship.

I read the entire “http://wiki.jmonkeyengine.org/doku.php/jme3:advanced:physics” and can’t understand why this is not working.

So:

Is kinematic really the way to go? Can I instead use static objects for the ship parts? When I tried this the ships physics moved around but I can’t get the visual mesh to follow the physics. The world coordinates of the spatial where the same as the world coordinates of the spatials physics…)

Why could it be that objects I put inside the ship do not follow the ship around even though they are in the same physics space and are set to “applyLocalPhysics” ?

Please help thanks

I work directly with rigidbodies, so i don’t know that part.

What I have to do, is to get the location of the ship,
and attach the player visuals to this ship, and offset them locally with the position of the actual physic that is inside the ship physic space.

eg if i am at 0,0,10 inside the ships physicspace, i must be rendered locally translated to the ship.

So the total position (wich is calcualted by the scenegraph) is
ship rotation& position + player in inner space position&rotation.

Eg i guess you misseed, that the physicobject inside the ships physicspace is relative to the ship, not to the world.

Ok, so lets leave out the visuals for now,

If I have a rigidbody inside the ship (for ex. a physics character or a simple box), it should move with the ship when I move and/or rotate the ship.

How do I achieve this ?

You don’t :smile:

You have two physicspaces they are completly seperate.

One represents your solarsystem.
One represetns your ships inside with me as a physicbody.

Now if I am inside my ship at 2,4,5 you know that i am relative to the ship translated.
If you know where the ship is, you know where I am in the solarsystem.

Since I am relative to the ship calculated, if you move the ship, I’m moved as well.

Basically it’s a scenegraph for nested physicspaces.

Does this work automatically or must the user place the astronaut relative to the solar system via custom code? If it’s automatic, that would be very cool. :chimpanzee_smile: If it’s not automatic, then it’s identical to my suggested solution.

it is the same, just explained differently i think

Erm… this is giving me a headache :smile:
I am in a scenario where I want to move the ship around while also moving inside that ship. Lets go back to the basics?

I was thinking one physics space for the solar system, and one physics space for every ship in that solar system.

The part I can’t get my head around is how I can clamp my player to the deck of the ship while AI is flying the ship and be able to use physics to not go through walls.

That being explained, I want to try something simple first:

Say I am on a terrain, imagine a 50x50 flat square hovering 10m above the ground.

This square is moving at 0.1m per second on the x axis.

When I stand on that plane I want to move with it and also walk around on it. If I stand still, I do not want to fall off.

This requires 2 physics spaces right?

How exactly would I add a rigid body on top of the moving square in such a way that it will not fall off when I rotate the moving square ?

How do I set up the physics space and rigid body (kinematic, mass etc…)?

Got any code examples please?

I can give you a code dump, i gues it wont help at all tho, ad you are missing the final “click”

But you are aware, that you need the ships collision 2 times? in both spaces?

Yes, and a way to know when the player enters/exits the ship

In my case I have a useable airlock :wink: That makes it easy, if player uses it , he gets transfered to the other.

Ok, I think I found a good way for you to pass knowledge along to me. My idea is to make a test case that we could add to jme3.1 test examples.

This test example would demonstrate the following:

  1. How to create a moving physics node
  2. How to transfer an object/character from one space to the another.

Quote from advancedPhysics:

“To use the local applying to simulate e.g. the internal physics system of a train passing by, simply create another BulletAppState and add all models with physics controls in local mode to a node. When you move the node the physics will happen all the same but the objects will move along with the node.”

I used TestBetterCharacter from jme3test.bullet to make a small testcase to demonstrate what I am trying to do.

I added a new platform in a new BulletAppState and added a cube ontop of that platform. These two objects are attached to a node which is attached to the rootnode. The quote says that moving the node will move the objects with it but I can’t achieve this. For now, the physics are not following the meshes.

The following is runnable and does not use any special librairies:

 /*
 * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. <p/>
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer. <p/> * Redistributions
 * in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other
 * materials provided with the distribution. <p/> * Neither the name of
 * 'jMonkeyEngine' nor the names of its contributors may be used to endorse or
 * promote products derived from this software without specific prior written
 * permission. <p/> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package jme3test.bullet;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.control.RigidBodyControl;
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.FastMath;  
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.system.AppSettings;

/**
 * A walking physical character followed by a 3rd person camera. (No animation.)
 *
 * @author normenhansen, zathras
 */
public class TestBetterCharacter extends SimpleApplication implements ActionListener {

private BulletAppState bulletAppState;
private BulletAppState movingBulletAppState;
private BetterCharacterControl physicsCharacter;
private Node characterNode;
private Node movingPhysicsNode;
private CameraNode camNode;

boolean rotate = false;
private Vector3f walkDirection = new Vector3f(0, 0, 0);
private Vector3f viewDirection = new Vector3f(0, 0, 1);
boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false,
        leftRotate = false, rightRotate = false;
private Vector3f normalGravity = new Vector3f(0, -9.81f, 0);
private Geometry planet;
private Geometry movingPlatform;

public static void main(String[] args) {
    TestBetterCharacter app = new TestBetterCharacter();
    AppSettings settings = new AppSettings(true);
    settings.setRenderer(AppSettings.LWJGL_OPENGL2);
    settings.setAudioRenderer(AppSettings.LWJGL_OPENAL);
    app.setSettings(settings);
    app.start();
}

@Override
public void simpleInitApp() {
    //setup keyboard mapping
    setupKeys();

    // activate physics
    bulletAppState = new BulletAppState();
    stateManager.attach(bulletAppState);
    bulletAppState.setDebugEnabled(true);
    
    movingBulletAppState = new BulletAppState();
    stateManager.attach(movingBulletAppState);
    movingBulletAppState.setDebugEnabled(true);
    movingPhysicsNode = new Node();
    rootNode.attachChild(movingPhysicsNode);
    
    // init a physics test scene
    PhysicsTestHelper.createPhysicsTestWorldSoccer(rootNode, assetManager, bulletAppState.getPhysicsSpace());
    PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace());
    setupPlanet();
    
    setupMovingPlatform();

    // Create a node for the character model
    characterNode = new Node("character node");
    characterNode.setLocalTranslation(new Vector3f(4, 5, 2));

    // Add a character control to the node so we can add other things and
    // control the model rotation
    physicsCharacter = new BetterCharacterControl(0.3f, 2.5f, 8f);
    characterNode.addControl(physicsCharacter);
    getPhysicsSpace().add(physicsCharacter);

    // Load model, attach to character node
    Node model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o");
    model.setLocalScale(1.50f);
    characterNode.attachChild(model);

    // Add character node to the rootNode
    rootNode.attachChild(characterNode);

    // Set forward camera node that follows the character, only used when
    // view is "locked"
    camNode = new CameraNode("CamNode", cam);
    camNode.setControlDir(ControlDirection.SpatialToCamera);
    camNode.setLocalTranslation(new Vector3f(0, 2, -6));
    Quaternion quat = new Quaternion();
    // These coordinates are local, the camNode is attached to the character node!
    quat.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y);
    camNode.setLocalRotation(quat);
    characterNode.attachChild(camNode);
    // Disable by default, can be enabled via keyboard shortcut
    camNode.setEnabled(false);
}

@Override
public void simpleUpdate(float tpf) {
    // Apply planet gravity to character if close enough (see below)
    checkPlanetGravity();

    // Get current forward and left vectors of model by using its rotation
    // to rotate the unit vectors
    Vector3f modelForwardDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_Z);
    Vector3f modelLeftDir = characterNode.getWorldRotation().mult(Vector3f.UNIT_X);

    // WalkDirection is global!
    // You *can* make your character fly with this.
    walkDirection.set(0, 0, 0);
    if (leftStrafe) {
        walkDirection.addLocal(modelLeftDir.mult(5));
    } else if (rightStrafe) {
        walkDirection.addLocal(modelLeftDir.negate().multLocal(5));
    }
    if (forward) {
        walkDirection.addLocal(modelForwardDir.mult(5));
    } else if (backward) {
        walkDirection.addLocal(modelForwardDir.negate().multLocal(5));
    }
    physicsCharacter.setWalkDirection(walkDirection);

    // ViewDirection is local to characters physics system!
    // The final world rotation depends on the gravity and on the state of
    // setApplyPhysicsLocal()
    if (leftRotate) {
        Quaternion rotateL = new Quaternion().fromAngleAxis(FastMath.PI * tpf, Vector3f.UNIT_Y);
        rotateL.multLocal(viewDirection);
    } else if (rightRotate) {
        Quaternion rotateR = new Quaternion().fromAngleAxis(-FastMath.PI * tpf, Vector3f.UNIT_Y);
        rotateR.multLocal(viewDirection);
    }
    physicsCharacter.setViewDirection(viewDirection);
    fpsText.setText("Touch da ground = " + physicsCharacter.isOnGround());
    if (!lockView) {
        cam.lookAt(characterNode.getWorldTranslation().add(new Vector3f(0, 2, 0)), Vector3f.UNIT_Y);
    }
    processPlatform(tpf);
}

private void processPlatform(float tpf){
    movingPhysicsNode.move(0.1f*tpf,0,0);
}

private void setupPlanet() {
    Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
    //immovable sphere with mesh collision shape
    Sphere sphere = new Sphere(64, 64, 20);
    planet = new Geometry("Sphere", sphere);
    planet.setMaterial(material);
    planet.setLocalTranslation(30, -15, 30);
    planet.addControl(new RigidBodyControl(0));
    planet.getControl(RigidBodyControl.class).setKinematic(true);
    rootNode.attachChild(planet);
    getPhysicsSpace().add(planet);
}

private void setupMovingPlatform(){
    Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    material.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
    
    //Platform for testing motion 
    Box platform = new Box(16, 0.3f, 8);
    movingPlatform = new Geometry("Platform", platform);
    movingPlatform.setMaterial(material);
    movingPlatform.setLocalTranslation(0, 1f, -20);
    movingPlatform.addControl(new RigidBodyControl(0));
    movingPlatform.getControl(RigidBodyControl.class).setKinematic(true);
    movingPlatform.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true);
    movingPhysicsNode.attachChild(movingPlatform);
    getMovingPhysicsSpace().add(movingPlatform);
    
    //Box put ontop of platform for localPhysics test
    
    Box box = new Box(1, 1, 1);
    Geometry cube = new Geometry("Cube", box);
    cube.setMaterial(material);
    cube.setLocalTranslation(0, 10f, -20);
    cube.addControl(new RigidBodyControl(1000));        
    cube.getControl(RigidBodyControl.class).setApplyPhysicsLocal(true);
    movingPhysicsNode.attachChild(cube);
    getMovingPhysicsSpace().add(cube);
}

private void checkPlanetGravity() {
    
    Vector3f planetDist = planet.getWorldTranslation().subtract(characterNode.getWorldTranslation());
    if (planetDist.length() < 24) {            
        physicsCharacter.setGravity(planetDist.normalizeLocal().multLocal(9.81f));            
    } else {
        physicsCharacter.setGravity(normalGravity);
    }
}

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

private PhysicsSpace getMovingPhysicsSpace() {
    return movingBulletAppState.getPhysicsSpace();
}

public void onAction(String binding, boolean value, float tpf) {
    if (binding.equals("Strafe Left")) {
        if (value) {
            leftStrafe = true;
        } else {
            leftStrafe = false;
        }
    } else if (binding.equals("Strafe Right")) {
        if (value) {
            rightStrafe = true;
        } else {
            rightStrafe = false;
        }
    } else if (binding.equals("Rotate Left")) {
        if (value) {
            leftRotate = true;
        } else {
            leftRotate = false;
        }
    } else if (binding.equals("Rotate Right")) {
        if (value) {
            rightRotate = true;
        } else {
            rightRotate = false;
        }
    } else if (binding.equals("Walk Forward")) {
        if (value) {
            forward = true;
        } else {
            forward = false;
        }
    } else if (binding.equals("Walk Backward")) {
        if (value) {
            backward = true;
        } else {
            backward = false;
        }
    } else if (binding.equals("Jump")) {
        physicsCharacter.jump();
    } else if (binding.equals("Duck")) {
        if (value) {
            physicsCharacter.setDucked(true);
        } else {
            physicsCharacter.setDucked(false);
        }
    } else if (binding.equals("Lock View")) {
        if (value && lockView) {
            lockView = false;
        } else if (value && !lockView) {
            lockView = true;
        }
        flyCam.setEnabled(!lockView);
        camNode.setEnabled(lockView);
    }
}
private boolean lockView = false;

private void setupKeys() {
    inputManager.addMapping("Strafe Left",
            new KeyTrigger(KeyInput.KEY_U),
            new KeyTrigger(KeyInput.KEY_Z));
    inputManager.addMapping("Strafe Right",
            new KeyTrigger(KeyInput.KEY_O),
            new KeyTrigger(KeyInput.KEY_X));
    inputManager.addMapping("Rotate Left",
            new KeyTrigger(KeyInput.KEY_J),
            new KeyTrigger(KeyInput.KEY_LEFT));
    inputManager.addMapping("Rotate Right",
            new KeyTrigger(KeyInput.KEY_L),
            new KeyTrigger(KeyInput.KEY_RIGHT));
    inputManager.addMapping("Walk Forward",
            new KeyTrigger(KeyInput.KEY_I),
            new KeyTrigger(KeyInput.KEY_UP));
    inputManager.addMapping("Walk Backward",
            new KeyTrigger(KeyInput.KEY_K),
            new KeyTrigger(KeyInput.KEY_DOWN));
    inputManager.addMapping("Jump",
            new KeyTrigger(KeyInput.KEY_F),
            new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addMapping("Duck",
            new KeyTrigger(KeyInput.KEY_G),
            new KeyTrigger(KeyInput.KEY_LSHIFT),
            new KeyTrigger(KeyInput.KEY_RSHIFT));
    inputManager.addMapping("Lock View",
            new KeyTrigger(KeyInput.KEY_RETURN));
    inputManager.addListener(this, "Strafe Left", "Strafe Right");
    inputManager.addListener(this, "Rotate Left", "Rotate Right");
    inputManager.addListener(this, "Walk Forward", "Walk Backward");
    inputManager.addListener(this, "Jump", "Duck", "Lock View");
}

@Override
public void simpleRender(RenderManager rm) {
}
}

P.S. What do I need to put at start and end of code sections again? I did this putting 4 spaces infront of all lines of code :slight_smile:

Hello Empire_Phoenix,

I would gladly take a copy of that airlock please.

Hi,

do you mean the code or the model?
The model is from Sci-Fi Models
the code is probably totally unuseable without my ES around it :stuck_out_tongue:

The code. I do not need it to be usable, I just though it would help me figure out how to get mine working.

Just to confirm: Your ships do fly? You do use a 2nd physics space for doing this?

Following code sections could be very usefull to me (anything that contains physics):

-Ship initialization

-Airlock initialization

-Player transfer from ship to ship OR from static object (space station?) to ship

-Anything you can think of that migh help :smile:

Thank you

Well I use a ES, so code is probably extremely different.
If you do not know what an ES is, brose the forums first, else you will have extreme problems to understand it.

Basically the airlock just changes the one component that contains the parent relation.
The humanoidsystem (subsystem for the pysicsystem) then work togther to determine that the parent changed, and recreate the humanoid in the correct physicspace.

private void processAirlock(final GameEntity usedEntity, final IEntity potentialNetworkClient, final IEntity humanoid, final AirlockState as) {
		final IsInVehicle humanoidVehicle = humanoid.get(IsInVehicle.class);
		final ShipPart linkVehicle = usedEntity.get(ShipPart.class);
		if (linkVehicle == null) {
			ServerUseSystem.LOGGER.warn("Airlock without spaceship link " + usedEntity);
			return;
		}
		final GameEntity vehicle = this.esc.getEntityByUUID(linkVehicle.getLinkTarget());
		if (vehicle == null) {
			ServerUseSystem.LOGGER.warn("Airlock vehicle invalid " + usedEntity);
			return;
		}
		final AbsolutePosition absPos = humanoid.get(AbsolutePosition.class);
		if (absPos == null) {
			ServerUseSystem.LOGGER.warn("AbsolutePosition for humanoid not existant " + humanoid);
			return;
		}
		final AbsoluteRotation absRot = humanoid.get(AbsoluteRotation.class);
		if (absRot == null) {
			ServerUseSystem.LOGGER.warn("AbsoluteRotation for humanoid not existant " + humanoid);
			return;
		}

		if (humanoidVehicle == null) {
			// outside to inside
			ServerUseSystem.LOGGER.debug("Airlocked humanodid {} to spaceship {}", humanoid.getUuid(), vehicle.getUuid());
			humanoid.setComponent(new IsInVehicle(vehicle));

			final Vector3f innerShipLocalOut = as.getInnerShip();
			final RelativeToParent baseOffset = usedEntity.get(RelativeToParent.class);
			final Vector3f spawnPoint = baseOffset.asVector3f().add(baseOffset.asQuaternion().mult(innerShipLocalOut));

			this.psys.addTask(new TeleportRelativeToParentTask(humanoid, vehicle.getUuid(), spawnPoint, absRot.asQuaternion(new Quaternion())));

			humanoid.setComponent(InSystem.class, null);
		} else {
			// inside to outside
			final Vector3f outerShipLocal = as.getOuterShip();

			final AbsolutePosition baseOffset = usedEntity.get(AbsolutePosition.class);
			final AbsoluteRotation rotation = usedEntity.get(AbsoluteRotation.class);

			final Vector3f spawnPoint = baseOffset.asVector3f(new Vector3f()).add(rotation.asQuaternion(new Quaternion()).mult(outerShipLocal));

			ServerUseSystem.LOGGER.debug("Airlocked humanodid {} to space {}", humanoid.getUuid(), ServerApplication.getLocalSystemID());
			humanoid.setComponent(IsInVehicle.class, null);
			this.psys.addTask(new TeleportRelativeToParentTask(humanoid, ServerApplication.getLocalSystemID(), spawnPoint, absRot.asQuaternion(new Quaternion())));
			humanoid.setComponent(InSystem.class, new InSystem(ServerApplication.getLocalSystemID()));
		}
		vehicle.setComponent(new UserEvent());
	}

And the other side

private void update() {
	for (final GameEntity newHumanoid : this.humanoidSet.getMatched()) {
		final VGSBetterCharacterControll controll = this.bcc.get(newHumanoid.getUuid());

		if (controll == null) {
			if (newHumanoid.get(DisablePhysics.class) != null) {
				continue;
			}
			final boolean success = this.tryCreate(newHumanoid);
			if (!success) {
				HumanoidSystem.LOGGER.warn("Unable to create humanoid for " + newHumanoid);
				continue;
			}
			continue;
		}

		final RelativeToParent parentRelation = newHumanoid.get(RelativeToParent.class);
		if (parentRelation.parent == null) {
			HumanoidSystem.LOGGER.debug("No parentPhysicsSpace for Humanoid " + newHumanoid);
			continue;
		}
		if (newHumanoid.get(DisablePhysics.class) != null) {
			HumanoidSystem.LOGGER.info("Found disabled VGSCharacterControll " + newHumanoid.getUuid());
			this.removeCharacterFromPhysicSpace(newHumanoid);
			continue;
		}

		if (parentRelation.getParent().equals(controll.getParentSpace())) {
			this.updateNormally(newHumanoid, controll, parentRelation);
			continue;
		}

		HumanoidSystem.LOGGER.info("Found VGSCharacterControll in wrong pspace, moving {} -> {}", controll.getParentSpace(), parentRelation.getParent());
		this.removeCharacterFromPhysicSpace(newHumanoid);
	}
}
2 Likes