/******************************************************************************* * Copyright 2014 See AUTHORS file. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.badlogic.gdx.ai.tests.steer.bullet.tests; import com.badlogic.gdx.ai.GdxAI; import com.badlogic.gdx.ai.msg.MessageManager; import com.badlogic.gdx.ai.msg.Telegram; import com.badlogic.gdx.ai.msg.Telegraph; import com.badlogic.gdx.ai.steer.behaviors.FollowPath; import com.badlogic.gdx.ai.steer.behaviors.Jump; import com.badlogic.gdx.ai.steer.behaviors.Jump.GravityComponentHandler; import com.badlogic.gdx.ai.steer.behaviors.Jump.JumpCallback; import com.badlogic.gdx.ai.steer.behaviors.Jump.JumpDescriptor; import com.badlogic.gdx.ai.steer.limiters.LinearLimiter; import com.badlogic.gdx.ai.steer.utils.paths.LinePath; import com.badlogic.gdx.ai.steer.utils.paths.LinePath.LinePathParam; import com.badlogic.gdx.ai.tests.SteeringBehaviorsTest; import com.badlogic.gdx.ai.tests.steer.bullet.BulletSteeringTest; import com.badlogic.gdx.ai.tests.steer.bullet.SteeringBulletEntity; import com.badlogic.gdx.ai.tests.utils.bullet.BulletEntity; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Matrix3; import com.badlogic.gdx.math.Matrix4; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.ui.Button; import com.badlogic.gdx.scenes.scene2d.ui.Label; import com.badlogic.gdx.scenes.scene2d.ui.SelectBox; import com.badlogic.gdx.scenes.scene2d.ui.Slider; import com.badlogic.gdx.scenes.scene2d.ui.Table; import com.badlogic.gdx.scenes.scene2d.ui.TextButton; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.utils.Array; /** A class to test and experiment with the {@link Jump} behavior. * @author davebaol */ public class BulletJumpTest extends BulletSteeringTest { static final GravityComponentHandler<Vector3> GRAVITY_COMPONENT_HANDLER = new GravityComponentHandler<Vector3>() { @Override public float getComponent (Vector3 vector) { return vector.y; } @Override public void setComponent (Vector3 vector, float value) { vector.y = value; } }; boolean drawDebug; ShapeRenderer shapeRenderer; SteeringBulletEntity character; Array<Vector3> wayPoints; LinePath<Vector3> linePath; FollowPath<Vector3, LinePathParam> followPathSB; JumpDescriptor<Vector3> jumpDescriptor; Jump<Vector3> jumpSB; int airbornePlanarVelocityToUse = 0; float runUpLength = 3.5f; public BulletJumpTest (SteeringBehaviorsTest container) { super(container, "Jump"); } @Override public void create () { super.create(); drawDebug = true; shapeRenderer = new ShapeRenderer(); Jump.DEBUG_ENABLED = true; BulletEntity ground = world.add("ground", 0f, 0f, 0f); ground.setColor(0.25f + 0.5f * (float)Math.random(), 0.25f + 0.5f * (float)Math.random(), 0.25f + 0.5f * (float)Math.random(), 1f); System.out.println("ground.body.getFriction()" + ground.body.getFriction()); ground.body.setFriction(0); BulletEntity characterBase = world.add("capsule", new Matrix4()); character = new SteeringBulletEntity(characterBase) { @Override public void update (float deltaTime) { super.update(deltaTime); // Should the character switch to Jump behavior? if (character.getSteeringBehavior() == followPathSB) { float d1 = followPathSB.getPathParam().getDistance(); float d2 = linePath.getSegments().get(linePath.getSegments().size - 2).getCumulativeLength(); float distFromTakeoffPoint = Math.abs(d1 - d2); if (distFromTakeoffPoint < runUpLength) { System.out.println("Switched to Jump behavior. Taking a run up..."); System.out.println("run up length = " + distFromTakeoffPoint); character.body.setDamping(0, 0); System.out.println("friction: " + character.body.getFriction()); character.body.setFriction(0); System.out.println("owner.linearVelocity = " + character.getLinearVelocity() + "; owner.linearSpeed = " + character.getLinearVelocity().len()); character.setSteeringBehavior(jumpSB); } } } }; character.setMaxLinearAcceleration(100); character.setMaxLinearSpeed(5); // Remove all stuff that causes jump failure // Notice that you might remove this on takeoff and restore on landing character.body.setSleepingThresholds(0, 0); character.body.setDamping(0, 0); character.body.setFriction(0); // character.body.setMassProps(1, new Vector3(0,0,0)); // character.body.setAnisotropicFriction(new Vector3(0,0,0)); // ??? wayPoints = createRandomPath(6, 20, 20, 30, 30, 1.5f); setCharacterPositionOnPath(); linePath = new LinePath<Vector3>(wayPoints, false); followPathSB = new FollowPath<Vector3, LinePathParam>(character, linePath, 0.5f) // // Setters below are only useful to arrive at the end of an open path .setArriveEnabled(false) // .setTimeToTarget(0.1f) // .setArrivalTolerance(0.5f) // .setDecelerationRadius(3); character.setSteeringBehavior(followPathSB); Vector3 takeoffPoint = wayPoints.peek(); Vector3 landingPoint = wayPoints.first(); System.out.println("takeoffPoint: " + takeoffPoint); System.out.println("landingPoint: " + landingPoint); jumpDescriptor = new JumpDescriptor<Vector3>(takeoffPoint, landingPoint); JumpCallback jumpCallback = new JumpCallback() { JumpDescriptor<Vector3> newJumpDescriptor = new JumpDescriptor<Vector3>(new Vector3(), new Vector3()); @Override public void reportAchievability (boolean achievable) { System.out.println("Jump Achievability = " + achievable); } @Override public void takeoff (float maxVerticalVelocity, float time) { System.out.println("Take off!!!"); System.out.println("Character Velocity = " + character.getLinearVelocity() + "; Speed = " + character.getLinearVelocity().len()); float h = maxVerticalVelocity * maxVerticalVelocity / (-2f * jumpSB.getGravity().y); System.out.println("jump height = " + h); switch (airbornePlanarVelocityToUse) { case 0: // Use character velocity on takeoff character.body.setLinearVelocity(character.body.getLinearVelocity().add(0, maxVerticalVelocity, 0)); break; case 1: // Use predicted velocity. We are cheating!!! Vector3 targetLinearVelocity = jumpSB.getTarget().getLinearVelocity(); character.body.setLinearVelocity(newJumpDescriptor.takeoffPosition.set(targetLinearVelocity.x, maxVerticalVelocity, targetLinearVelocity.z)); break; case 2: // Calculate and use exact velocity. We are shamelessly cheating!!! Vector3 newLinearVelocity = character.body.getLinearVelocity(); newJumpDescriptor.set(character.getPosition(), jumpSB.getJumpDescriptor().landingPosition); System.out.println("character.pos = " + character.getPosition()); time = jumpSB.calculateAirborneTimeAndVelocity(newLinearVelocity, newJumpDescriptor, jumpSB.getLimiter() .getMaxLinearSpeed()); character.body.setLinearVelocity(newLinearVelocity.add(0, maxVerticalVelocity, 0)); break; } Telegraph telegraph = new Telegraph() { @Override public boolean handleMessage (Telegram telegram) { if (telegram.message == 1) { System.out.println("Switching to FollowPath"); System.out.println("owner.linearVelocity = " + character.getLinearVelocity() + "; owner.linearSpeed = " + character.getLinearVelocity().len()); character.setSteeringBehavior(followPathSB); jumpSB.setJumpDescriptor(jumpDescriptor); // prepare for a new jump return true; } return false; } }; MessageManager.getInstance().dispatchMessage(time, telegraph, telegraph, 1); } }; jumpSB = new Jump<Vector3>(character, jumpDescriptor, world.gravity, GRAVITY_COMPONENT_HANDLER, jumpCallback) // .setMaxVerticalVelocity(9) // .setTakeoffPositionTolerance(.3f) // .setTakeoffVelocityTolerance(2f) // .setTimeToTarget(.1f); // Setup the limiter for the run up jumpSB.setLimiter(new LinearLimiter(Float.POSITIVE_INFINITY, character.getMaxLinearSpeed() * 3)); Table detailTable = new Table(container.skin); detailTable.row(); final Label labelRunUpLenght = new Label("Run Up Length [" + runUpLength + "]", container.skin); detailTable.add(labelRunUpLenght); detailTable.row(); Slider sliderRunUpLenght = new Slider(0.1f, 4f, 0.1f, false, container.skin); sliderRunUpLenght.setValue(runUpLength); sliderRunUpLenght.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { Slider slider = (Slider)actor; runUpLength = slider.getValue(); labelRunUpLenght.setText("Run Up Length [" + slider.getValue() + "]"); } }); detailTable.add(sliderRunUpLenght); detailTable.row(); final Label labelTakeoffPosTol = new Label("Takeoff Pos.Tolerance [" + jumpSB.getTakeoffPositionTolerance() + "]", container.skin); detailTable.add(labelTakeoffPosTol); detailTable.row(); Slider takeoffPosTol = new Slider(0.1f, 5f, 0.1f, false, container.skin); takeoffPosTol.setValue(jumpSB.getTakeoffPositionTolerance()); takeoffPosTol.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { Slider slider = (Slider)actor; jumpSB.setTakeoffPositionTolerance(slider.getValue()); labelTakeoffPosTol.setText("Takeoff Pos.Tolerance [" + slider.getValue() + "]"); } }); detailTable.add(takeoffPosTol); detailTable.row(); final Label labelTakeoffVelTol = new Label("Takeoff Vel.Tolerance [" + jumpSB.getTakeoffVelocityTolerance() + "]", container.skin); detailTable.add(labelTakeoffVelTol); detailTable.row(); Slider takeoffVelTol = new Slider(0.1f, 10f, 0.1f, false, container.skin); takeoffVelTol.setValue(jumpSB.getTakeoffVelocityTolerance()); takeoffVelTol.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { Slider slider = (Slider)actor; jumpSB.setTakeoffVelocityTolerance(slider.getValue()); labelTakeoffVelTol.setText("Takeoff Vel.Tolerance [" + slider.getValue() + "]"); } }); detailTable.add(takeoffVelTol); detailTable.row(); final Label labelMaxVertVel = new Label("Max.Vertical Vel. [" + jumpSB.getMaxVerticalVelocity() + "]", container.skin); detailTable.add(labelMaxVertVel); detailTable.row(); Slider maxVertVel = new Slider(1f, 15f, 0.5f, false, container.skin); maxVertVel.setValue(jumpSB.getMaxVerticalVelocity()); maxVertVel.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { Slider slider = (Slider)actor; jumpSB.setMaxVerticalVelocity(slider.getValue()); labelMaxVertVel.setText("Max.Vertical Vel. [" + slider.getValue() + "]"); } }); detailTable.add(maxVertVel); detailTable.row(); addSeparator(detailTable); detailTable.row(); final Label labelJumpVel = new Label("Airborne Planar Velocity To Use", container.skin); detailTable.add(labelJumpVel); detailTable.row(); SelectBox<String> jumpVel = new SelectBox<String>(container.skin); jumpVel.setItems(new String[] {"Character Velocity on Takeoff", "Predicted Velocity (Cheat!!!)", "Calculate Exact Velocity (Cheat!!!)"}); jumpVel.setSelectedIndex(0); jumpVel.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { @SuppressWarnings("unchecked") SelectBox<String> selectBox = (SelectBox<String>)actor; airbornePlanarVelocityToUse = selectBox.getSelectedIndex(); } }); detailTable.add(jumpVel); detailTable.row(); addSeparator(detailTable); detailTable.row(); Button buttonRestart = new TextButton("Restart", container.skin); buttonRestart.addListener(new ChangeListener() { @Override public void changed (ChangeEvent event, Actor actor) { setCharacterPositionOnPath(); character.setSteeringBehavior(followPathSB); } }); detailTable.add(buttonRestart); detailWindow = createDetailWindow(detailTable); } @Override public void update () { MessageManager.getInstance().update(); character.update(GdxAI.getTimepiece().getDeltaTime()); super.update(); } @Override public void draw () { super.draw(); if (drawDebug) { // Draw path shapeRenderer.begin(ShapeType.Line); shapeRenderer.setColor(0, 1, 0, 1); shapeRenderer.setProjectionMatrix(camera.combined); for (int i = 0; i < wayPoints.size; i++) { int next = (i + 1) % wayPoints.size; if (next != 0 || !linePath.isOpen()) shapeRenderer.line(wayPoints.get(i), wayPoints.get(next)); } shapeRenderer.end(); // Draw hole to jump shapeRenderer.begin(ShapeType.Line); shapeRenderer.setColor(1, 0, 0, 1); shapeRenderer.setProjectionMatrix(camera.combined); shapeRenderer.line(jumpDescriptor.takeoffPosition, jumpDescriptor.landingPosition); shapeRenderer.end(); } } @Override public void dispose () { super.dispose(); shapeRenderer.dispose(); MessageManager.getInstance().clear(); } private void setCharacterPositionOnPath () { character.transform.setToTranslation(wayPoints.get(1)); character.body.setWorldTransform(character.transform); } private static final Matrix3 tmpMatrix3 = new Matrix3(); private static final Vector2 tmpVector2 = new Vector2(); /** Creates a random path which is bound by rectangle described by the min/max values */ private static Array<Vector3> createRandomPath (int numWaypoints, float minX, float minY, float maxX, float maxY, float height) { Array<Vector3> wayPoints = new Array<Vector3>(numWaypoints); wayPoints.size = numWaypoints; float midX = (maxX + minX) / 2f; float midY = (maxY + minY) / 2f; float smaller = Math.min(midX, midY); float spacing = MathUtils.PI2 / (numWaypoints - 0); for (int i = 0; i < numWaypoints - 2; i++) { float radialDist = MathUtils.random(smaller * 0.2f, smaller); tmpVector2.set(radialDist, 0.0f); // rotates the specified vector angle rads around the origin // init and rotate the transformation matrix tmpMatrix3.idt().rotateRad(i * spacing); // now transform the object's vertices tmpVector2.mul(tmpMatrix3); wayPoints.set(i + 1, new Vector3(tmpVector2.x, height, tmpVector2.y)); System.out.println((i + 1) + ": " + wayPoints.get(i + 1)); } Vector3 midpoint = new Vector3(wayPoints.get(1)).add(wayPoints.get(numWaypoints - 2)).scl(0.5f); System.out.println("midpoint = " + midpoint); // Set the landing point wayPoints.set(0, new Vector3(wayPoints.get(1)).add(midpoint).scl(1f / 3f)); wayPoints.get(0).y = height; // Set the takeoff point wayPoints.set(numWaypoints - 1, new Vector3(midpoint).add(wayPoints.get(numWaypoints - 2)).scl(1f / 3f)); wayPoints.get(numWaypoints - 1).y = height; System.out.println("0: " + wayPoints.first()); System.out.println((numWaypoints - 1) + ": " + wayPoints.peek()); return wayPoints; } }