/*******************************************************************************
* 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.scene2d.tests;
import com.badlogic.gdx.ai.steer.behaviors.FollowFlowField;
import com.badlogic.gdx.ai.steer.behaviors.FollowFlowField.FlowField;
import com.badlogic.gdx.ai.tests.SteeringBehaviorsTest;
import com.badlogic.gdx.ai.tests.steer.scene2d.Scene2dSteeringTest;
import com.badlogic.gdx.ai.tests.steer.scene2d.SteeringActor;
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.Vector2;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Slider;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.Array;
/** A class to test and experiment with the {@link FollowFlowField} behavior.
*
* @autor davebaol */
public class Scene2dFollowFlowFieldTest extends Scene2dSteeringTest {
boolean drawDebug;
ShapeRenderer shapeRenderer;
SteeringActor character;
RandomFlowField2DWithRepulsors flowField;
public Scene2dFollowFlowFieldTest (SteeringBehaviorsTest container) {
super(container, "Follow Flow Field");
}
@Override
public void create () {
super.create();
drawDebug = true;
shapeRenderer = new ShapeRenderer();
// Create obstacles
Array<SteeringActor> obstacles = new Array<SteeringActor>();
for (int i = 0; i < 4; i++) {
SteeringActor obstacle = new SteeringActor(container.cloud, false);
setRandomNonOverlappingPosition(obstacle, obstacles, 100);
obstacles.add(obstacle);
testTable.addActor(obstacle);
}
character = new SteeringActor(container.badlogicSmall, false);
character.setMaxLinearSpeed(300);
character.setMaxLinearAcceleration(400);
flowField = new RandomFlowField2DWithRepulsors(container.stageWidth, container.stageHeight, container.badlogicSmall.getRegionWidth(), obstacles);
final FollowFlowField<Vector2> followFlowFieldSB = new FollowFlowField<Vector2>(character, flowField);
character.setSteeringBehavior(followFlowFieldSB);
testTable.addActor(character);
character.setPosition(container.stageWidth / 2, container.stageHeight / 2, Align.center);
Table detailTable = new Table(container.skin);
detailTable.row();
addMaxLinearAccelerationController(detailTable, character, 0, 10000, 20);
detailTable.row();
addMaxLinearSpeedController(detailTable, character);
detailTable.row();
final Label labelPredictionTime = new Label("Prediction Time [" + followFlowFieldSB.getPredictionTime() + " sec.]", container.skin);
detailTable.add(labelPredictionTime);
detailTable.row();
Slider predictionTime = new Slider(0, 3, .1f, false, container.skin);
predictionTime.setValue(followFlowFieldSB.getPredictionTime());
predictionTime.addListener(new ChangeListener() {
@Override
public void changed (ChangeEvent event, Actor actor) {
Slider slider = (Slider)actor;
followFlowFieldSB.setPredictionTime(slider.getValue());
labelPredictionTime.setText("Prediction Time [" + slider.getValue() + " sec.]");
}
});
detailTable.add(predictionTime);
detailTable.row();
addSeparator(detailTable);
detailTable.row();
CheckBox debug = new CheckBox("Draw Flow Field", container.skin);
debug.setChecked(drawDebug);
debug.addListener(new ClickListener() {
@Override
public void clicked (InputEvent event, float x, float y) {
CheckBox checkBox = (CheckBox)event.getListenerActor();
drawDebug = checkBox.isChecked();
}
});
detailTable.add(debug);
detailWindow = createDetailWindow(detailTable);
}
Vector2 tmp1 = new Vector2();
Vector2 tmp2 = new Vector2();
@Override
public void draw () {
if (drawDebug) {
shapeRenderer.begin(ShapeType.Line);
shapeRenderer.setColor(0, 1, 0, 1);
Vector2[][] field = flowField.field;
float resolution = flowField.resolution;
float scaledResolution = resolution * .7f;
float halfResolution = resolution * .5f;
int columns = flowField.columns;
int rows = flowField.rows;
for (int i = 0; i < columns; i++) {
for (int j = 0; j < rows; j++) {
Vector2 flow = field[i][j];
tmp2.set(i * resolution + halfResolution, j * resolution + halfResolution); // midpoint
tmp2.mulAdd(flow, scaledResolution / 2); // end point
tmp1.set(tmp2).mulAdd(flow, -scaledResolution); // start point
shapeRenderer.line(tmp1.x, tmp1.y, tmp2.x, tmp2.y);
shapeRenderer.circle(tmp2.x, tmp2.y, 1.5f);
}
}
shapeRenderer.end();
}
}
@Override
public void dispose () {
super.dispose();
shapeRenderer.dispose();
}
static class RandomFlowField2DWithRepulsors implements FlowField<Vector2> {
Vector2[][] field;
int rows, columns;
int resolution;
public RandomFlowField2DWithRepulsors (float width, float height, int resolution, Array<SteeringActor> obstacles) {
this.resolution = resolution;
this.columns = MathUtils.ceil(width / resolution);
this.rows = MathUtils.ceil(height / resolution);
this.field = new Vector2[columns][rows];
for (int i = 0; i < columns; i++) {
ROWS:
for (int j = 0; j < rows; j++) {
for (int k = 0; k < obstacles.size; k++) {
SteeringActor obstacle = obstacles.get(k);
if (obstacle.getPosition().dst(resolution * (i + .5f), resolution * (j + .5f)) < obstacle.getBoundingRadius() + 40) {
field[i][j] = new Vector2(resolution * (i + .5f), resolution * (j + .5f)).sub(obstacle.getPosition()).nor();
continue ROWS;
}
}
field[i][j] = new Vector2(MathUtils.random(-1f, 1f), MathUtils.random(-1f, 1f)).nor();
}
}
}
@Override
public Vector2 lookup (Vector2 position) {
int column = (int)MathUtils.clamp(position.x / resolution, 0, columns - 1);
int row = (int)MathUtils.clamp(position.y / resolution, 0, rows - 1);
return field[column][row];
}
}
}