Hi all,
I've been toying around with the physics library (for reference, I got it from SVN) and it looks great.
However, for my program I'm working on, I need to be able to control objects more or less more or less the way "I want".
I've been trying to implement movement through 3 systems; velocity, force and surfacemotion. So far, velocity resulted in the best behavior, but I wouldn't be surprised if the results for the other 2 were better with a better algorithm.
The points that I'm having problems with are the following:
- When I move an object over the terrain, it shouldn't get 'airborne' when it hits a bump (just as a human doesn't get airborne when he walks over a small bump).
- when 2 objects collide, the object that was standing still (that is a DynamicPhysicsNode but just isn't moving at the time) should slightly move aside to let the other object pass. Block the moving object altogether is valid as well, as long as the moving object doesn't 'drag' the still object away. Would be great if it could be specified per object
- moving object doesn't get airborne when it collides with a still object.
I've made a test case with 1 object that tries to move towards the position when you left click on the terrain
100 other objects that are put in a grid (before falling down)
F5 enables/disables the 100 objects trying to get back to the original grid (but then on the terrain instead of in the air)
F6 toggles movement style. it rotates between velocity, force or surfacemotion. For the user controlled object and the 100 others when it is enabled (with F5)
F7 moves the center of mass of the objects from the center to the bottom and back when pressed again
The code is loosely based on the TestGenerateTerrain from the Physics library and thus needs the jmetest library for images for the terrain (should be easy to solve if not present).
The problem seems pretty common to me, yet I've been struggling with it
I'm looking forward to any suggestions and feedback.
Thanks in advance!
package YawnPhysics;
import javax.swing.ImageIcon;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.image.Texture.ApplyMode;
import com.jme.image.Texture.CombinerFunctionRGB;
import com.jme.image.Texture.CombinerOperandRGB;
import com.jme.image.Texture.CombinerScale;
import com.jme.image.Texture.CombinerSource;
import com.jme.image.Texture.MagnificationFilter;
import com.jme.image.Texture.MinificationFilter;
import com.jme.image.Texture.WrapMode;
import com.jme.input.InputHandler;
import com.jme.input.KeyInput;
import com.jme.light.DirectionalLight;
import com.jme.scene.state.CullState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.CullState.Face;
import com.jme.util.TextureManager;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.StaticPhysicsNode;
import com.jmex.physics.util.SimplePhysicsGame;
import com.jmex.terrain.TerrainPage;
import com.jmex.terrain.util.FaultFractalHeightMap;
import com.jmex.terrain.util.ProceduralTextureGenerator;
import com.jme.input.MouseInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.intersection.BoundingPickResults;
import com.jme.intersection.PickResults;
import com.jme.math.Ray;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.Text;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Sphere;
import com.jme.scene.state.BlendState;
import com.jme.system.DisplaySystem;
import com.jmetest.physics.TestGenerateTerrain;
import com.jmex.physics.material.Material;
import com.jmex.terrain.util.BresenhamTerrainPicker;
import java.util.logging.Level;
import java.util.logging.Logger;
public class YawnPhysics extends SimplePhysicsGame {
Node terrainNode;
DynamicPhysicsNode movingDPN;
final Vector3f targetloc = new Vector3f();
DynamicPhysicsNode[] otherDPN;
Vector3f[] otherLoc;
boolean otherHoldPosition = false;
MovementStyle movementStyle = MovementStyle.VELOCITY;
boolean centeredMasses = true;
Text[] labels;
@Override
protected void simpleInitGame() {
terrainNode = new Node("terrainNode");
rootNode.attachChild(terrainNode);
final StaticPhysicsNode staticNode = getPhysicsSpace().createStaticNode();
Spatial terrain = createTerrain();
staticNode.attachChild(terrain);
staticNode.getLocalTranslation().set(0, -150, 0);
staticNode.generatePhysicsGeometry();
terrainNode.attachChild(staticNode);
final StaticPhysicsNode wallStaticNode = getPhysicsSpace().createStaticNode();
Spatial wall = createWall();
wallStaticNode.attachChild(wall);
wallStaticNode.getLocalTranslation().set(-30, -20, 0);
wallStaticNode.generatePhysicsGeometry();
terrainNode.attachChild(wallStaticNode);
showPhysics = true;
MouseInput.get().setCursorVisible(true);
//new code
labels = new Text[3];
for (int i = 0; i < labels.length; i++) {
labels[i] = Text.createDefaultTextLabel("label" + i, "label" + i);
labels[i].setLocalTranslation(new Vector3f(0, i * 20 + 10, 1));
rootNode.attachChild(labels[i]);
}
input.addAction(new InputAction() {
public void performAction(InputActionEvent evt) {
if (evt.getTriggerPressed()) {
otherHoldPosition = !otherHoldPosition;
}
}
}, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_F5, InputHandler.AXIS_NONE, false);
input.addAction(new InputAction() {
public void performAction(InputActionEvent evt) {
if (evt.getTriggerPressed()) {
switch (movementStyle) {
case VELOCITY:
movementStyle = MovementStyle.FORCE;
break;
case FORCE:
movementStyle = movementStyle.SURFACEMOTION;
break;
case SURFACEMOTION:
movingDPN.getMaterial().setSurfaceMotion(Vector3f.ZERO);
for (int i = 0; i < otherDPN.length; i++) {
otherDPN[i].getMaterial().setSurfaceMotion(Vector3f.ZERO);
}
default:
movementStyle = movementStyle.VELOCITY;
}
}
}
}, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_F6, InputHandler.AXIS_NONE, false);
input.addAction(new InputAction() {
public void performAction(InputActionEvent evt) {
if (evt.getTriggerPressed()) {
centeredMasses = !centeredMasses;
Vector3f position = new Vector3f(0, 0, 0);
if (!centeredMasses) {
position.y = -0.5f;
}
movingDPN.setCenterOfMass(position);
for (int i = 0; i < otherDPN.length; i++) {
otherDPN[i].setCenterOfMass(position);
}
}
}
}, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_F7, InputHandler.AXIS_NONE, false);
createSelection();
movingDPN = createMovingObject(new Vector3f(-2, 0, -2));
otherDPN = new DynamicPhysicsNode[100];
otherLoc = new Vector3f[100];
int k = 0;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
otherLoc[k] = new Vector3f(i * 2, 0, j * 2);
otherDPN[k] = createMovingObject(otherLoc[k]);
k++;
}
}
}
private Spatial createTerrain() {
DirectionalLight dl = new DirectionalLight();
dl.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
dl.setDirection(new Vector3f(1, -0.5f, 1));
dl.setEnabled(true);
lightState.attach(dl);
DirectionalLight dr = new DirectionalLight();
dr.setEnabled(true);
dr.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
dr.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
dr.setDirection(new Vector3f(0.5f, -0.5f, 0));
lightState.attach(dr);
display.getRenderer().setBackgroundColor(new ColorRGBA(0.5f, 0.5f, 0.5f, 1));
FaultFractalHeightMap heightMap = new FaultFractalHeightMap(257, 32, 0, 255,
0.75f, 3);
Vector3f terrainScale = new Vector3f(10, 1, 10);
heightMap.setHeightScale(0.001f);
TerrainPage page = new TerrainPage("Terrain", 33, heightMap.getSize(), terrainScale,
heightMap.getHeightMap());
page.setDetailTexture(1, 16);
CullState cs = DisplaySystem.getDisplaySystem().getRenderer().createCullState();
cs.setCullFace(Face.Back);
cs.setEnabled(true);
page.setRenderState(cs);
ProceduralTextureGenerator pt = new ProceduralTextureGenerator(heightMap);
pt.addTexture(new ImageIcon(TestGenerateTerrain.class.getClassLoader().getResource(
"jmetest/data/texture/grassb.png")), -128, 0, 128);
pt.addTexture(new ImageIcon(TestGenerateTerrain.class.getClassLoader().getResource(
"jmetest/data/texture/dirt.jpg")), 0, 128, 255);
pt.addTexture(new ImageIcon(TestGenerateTerrain.class.getClassLoader().getResource(
"jmetest/data/texture/highest.jpg")), 128, 255, 384);
pt.createTexture(512);
TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
ts.setEnabled(true);
Texture t1 = TextureManager.loadTexture(pt.getImageIcon().getImage(),
MinificationFilter.Trilinear, MagnificationFilter.Bilinear, true);
ts.setTexture(t1, 0);
Texture t2 = TextureManager.loadTexture(TestGenerateTerrain.class.getClassLoader().
getResource("jmetest/data/texture/Detail.jpg"),
MinificationFilter.Trilinear, MagnificationFilter.Bilinear);
ts.setTexture(t2, 1);
t2.setWrap(WrapMode.Repeat);
t1.setApply(ApplyMode.Combine);
t1.setCombineFuncRGB(CombinerFunctionRGB.Modulate);
t1.setCombineSrc0RGB(CombinerSource.CurrentTexture);
t1.setCombineOp0RGB(CombinerOperandRGB.SourceColor);
t1.setCombineSrc1RGB(CombinerSource.PrimaryColor);
t1.setCombineOp1RGB(CombinerOperandRGB.SourceColor);
t1.setCombineScaleRGB(CombinerScale.One);
t2.setApply(ApplyMode.Combine);
t2.setCombineFuncRGB(CombinerFunctionRGB.AddSigned);
t2.setCombineSrc0RGB(CombinerSource.CurrentTexture);
t2.setCombineOp0RGB(CombinerOperandRGB.SourceColor);
t2.setCombineSrc1RGB(CombinerSource.Previous);
t2.setCombineOp1RGB(CombinerOperandRGB.SourceColor);
t2.setCombineScaleRGB(CombinerScale.One);
page.setRenderState(ts);
return page;
}
private Spatial createWall() {
Box b = new Box("Wall", null, 2, 10, 30);
b.setModelBound(new BoundingBox());
b.updateModelBound();
b.setDefaultColor(new ColorRGBA(0.5f, 0, 0, 0.5f));
b.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
b.setLightCombineMode(Spatial.LightCombineMode.Off);
BlendState bs = DisplaySystem.getDisplaySystem().getRenderer().createBlendState();
bs.setBlendEnabled(true);
bs.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
bs.setDestinationFunction(BlendState.DestinationFunction.One);
bs.setTestEnabled(true);
bs.setTestFunction(BlendState.TestFunction.GreaterThan);
bs.setEnabled(true);
b.setRenderState(bs);
return b;
}
private DynamicPhysicsNode createMovingObject(Vector3f location) {
if (location == null) {
location = new Vector3f();
}
Spatial spatial = new Box("meshbox", new Vector3f(), 0.5f, 0.5f, 0.5f);
spatial.setModelBound(new BoundingBox());
spatial.updateModelBound();
DynamicPhysicsNode dpn = getPhysicsSpace().createDynamicNode();
dpn.attachChild(spatial);
dpn.generatePhysicsGeometry();
dpn.computeMass();
dpn.setMaterial(new Material());
dpn.setCenterOfMass(new Vector3f(0, -0.5f, 0));
dpn.getLocalTranslation().set(location);
rootNode.attachChild(dpn);
return dpn;
}
private void createSelection() {
Sphere s = new Sphere("Selection", 11, 11, 1);
s.setDefaultColor(new ColorRGBA(0, 128, 128, 0.1f));
s.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
s.setLightCombineMode(Spatial.LightCombineMode.Off);
BlendState bs = DisplaySystem.getDisplaySystem().getRenderer().createBlendState();
bs.setBlendEnabled(true);
bs.setSourceFunction(BlendState.SourceFunction.SourceAlpha);
bs.setDestinationFunction(BlendState.DestinationFunction.One);
bs.setTestEnabled(true);
bs.setTestFunction(BlendState.TestFunction.GreaterThan);
bs.setEnabled(true);
s.setRenderState(bs);
Node target = new Node();
target.attachChild(s);
target.setLocalTranslation(targetloc); //from now on, target will be where targetloc is pointing at
rootNode.attachChild(target);
}
@Override
protected void simpleUpdate() {
cameraInputHandler.setEnabled(MouseInput.get().isButtonDown(1));
labels[0].print("F5 toggle other hold, now " + otherHoldPosition);
labels[1].print("F6 change movement style, now " + movementStyle.toString());
labels[2].print("F7 toggle centered masses, now " + centeredMasses);
updateTarget();
updateMovement(movingDPN, targetloc, true);
for (int i = 0; i < otherDPN.length; i++) {
updateMovement(otherDPN[i], otherLoc[i], otherHoldPosition);
}
}
private void updateMovement(final DynamicPhysicsNode dpn, final Vector3f target, boolean active) {
switch (movementStyle) {
case VELOCITY:
updateMovementVelocity(dpn, target, active);
break;
case FORCE:
updateMovementForce(dpn, target, active);
break;
case SURFACEMOTION:
updateMovementSurfaceMotion(dpn, target, active);
break;
}
}
private void updateMovementVelocity(DynamicPhysicsNode dpn, Vector3f target, boolean active) {
if (active == false) {
return;
}
Vector3f diff = target.subtract(dpn.getLocalTranslation());
diff.normalizeLocal().multLocal(10);
diff.setY(dpn.getLinearVelocity(null).getY());
dpn.setLinearVelocity(diff);
}
private void updateMovementForce(DynamicPhysicsNode dpn, Vector3f target, boolean active) {
if (active == false) {
dpn.addForce(new Vector3f(0, -10, 0));
return;
}
Vector3f diff = target.subtract(dpn.getLocalTranslation());
diff.normalizeLocal().multLocal(200);
diff.y = -10;
dpn.addForce(diff);
}
private void updateMovementSurfaceMotion(DynamicPhysicsNode dpn, Vector3f target, boolean active) {
if (active == false) {
dpn.getMaterial().setSurfaceMotion(Vector3f.ZERO);
return;
}
Vector3f diff = target.subtract(dpn.getLocalTranslation());
diff.normalizeLocal().multLocal(10);
dpn.getMaterial().setSurfaceMotion(diff);
}
private void updateTarget() {
if (MouseInput.get().isButtonDown(0) == false) {
return;
}
Vector2f screenPos = new Vector2f(MouseInput.get().getXAbsolute(), MouseInput.get().getYAbsolute());
Vector3f worldCoords = display.getWorldCoordinates(screenPos, 0);
Vector3f worldCoords2 = display.getWorldCoordinates(screenPos, 1);
Ray mouseRay = new Ray(worldCoords, worldCoords2.subtractLocal(worldCoords).normalizeLocal());
PickResults pickResults = new BoundingPickResults();
pickResults.setCheckDistance(true);
terrainNode.findPick(mouseRay, pickResults);
for (int i = 0; i < pickResults.getNumber(); i++) {
BresenhamTerrainPicker terrainPicker = new BresenhamTerrainPicker(pickResults.getPickData(i).getTargetMesh());
Vector3f res = terrainPicker.getTerrainIntersection(mouseRay, null);
if (res != null) {
targetloc.set(res);
break;
}
//TODO find intersections with other elements other than TerrainBlocks
}
}
public static void main(String[] args) {
Logger.getLogger("").setLevel(Level.WARNING); // to see the important stuff
new YawnPhysics().start();
}
public enum MovementStyle {
VELOCITY,
FORCE,
SURFACEMOTION,
}
}