Skip to content

Instantly share code, notes, and snippets.

@xoppa
Created September 21, 2013 21:21
Show Gist options
  • Save xoppa/6654328 to your computer and use it in GitHub Desktop.
Save xoppa/6654328 to your computer and use it in GitHub Desktop.
package com.badlogic.gdx.tests.g3d;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight;
import com.badlogic.gdx.graphics.g3d.environment.Environment;
import com.badlogic.gdx.graphics.g3d.utils.CameraInputController;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.math.collision.Ray;
import com.badlogic.gdx.tests.utils.GdxTest;
import com.badlogic.gdx.utils.Array;
public class Basic3DTest extends GdxTest /* GdxTest = extends InputAdapter implements ApplicationListener */ {
public PerspectiveCamera cam;
public CameraInputController camController;
public ModelBatch modelBatch;
public AssetManager assets;
public Array<ModelInstance> instances = new Array<ModelInstance>();
public Environment lights;
public boolean loading;
public int currentDice = -1;
public float diceDistance;
public float diceRadiusSquared;
public float diceRadius;
public Vector3 previousTouchPoint = new Vector3();
public Vector3 currentTouchPoint = new Vector3();
public Vector3 touchPos = new Vector3();
public Vector3 xAxis = new Vector3();
public Vector3 yAxis = new Vector3();
public Vector3 zAxis = new Vector3();
public Array<ModelInstance> dices = new Array<ModelInstance>();
@Override
public void create () {
modelBatch = new ModelBatch();
lights = new Environment();
lights.ambientLight.set(0.4f, 0.4f, 0.4f, 1f);
lights.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));
cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
cam.position.set(0f, 5f, 8f);
cam.lookAt(0,0,0);
cam.near = 0.1f;
cam.far = 300f;
cam.normalizeUp();
cam.update();
camController = new CameraInputController(cam);
Gdx.input.setInputProcessor(new InputMultiplexer(this, camController));
assets = new AssetManager();
assets.load("data/g3d/test/dieYellow.g3db", Model.class);
loading = true;
}
private void doneLoading() {
Model model = assets.get("data/g3d/test/dieYellow.g3db", Model.class);
BoundingBox bounds = new BoundingBox();
model.getNode("Die").calculateBoundingBox(bounds);
diceRadius = .5f * bounds.getDimensions().len();
diceRadiusSquared = diceRadius * diceRadius;
for (float x = -5f; x <= 7f; x += 5f) {
ModelInstance aDie = new ModelInstance(model, "Die");
aDie.transform.setToTranslation(x, 0, 0);
instances.add(aDie);
dices.add(aDie);
}
loading = false;
}
@Override
public void render () {
if (loading && assets.update())
doneLoading();
camController.update();
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
modelBatch.begin(cam);
modelBatch.render(dices, lights);
modelBatch.end();
}
@Override
public void dispose () {
modelBatch.dispose();
dices.clear();
assets.dispose();
}
@Override
public void resize(int width, int height) {
cam.viewportHeight = width;
cam.viewportHeight = height;
cam.update();
}
@Override public void pause() { }
@Override public void resume() { }
@Override
public boolean touchDragged (int screenX, int screenY, int pointer) {
if (currentDice < 0)
return false;
Ray ray = cam.getPickRay(screenX, screenY);
currentTouchPoint.set(ray.direction).scl(diceDistance).add(ray.origin);
float degreesY = 90 * (currentTouchPoint.x - previousTouchPoint.x) / diceRadius;
float degreesX = 90 * (currentTouchPoint.y - previousTouchPoint.y) / diceRadius;
previousTouchPoint.set(currentTouchPoint);
rotateDice(dices.get(currentDice).transform, xAxis, degreesX, yAxis, degreesY);
return true;
}
@Override
public boolean touchDown (int screenX, int screenY, int pointer, int button) {
Ray ray = cam.getPickRay(screenX, screenY);
currentDice = getTouchedInstance(touchPos, dices, diceRadiusSquared, ray);
if (currentDice < 0)
return false;
diceDistance = cam.position.dst(touchPos);
previousTouchPoint.set(ray.direction).scl(diceDistance).add(ray.origin);
// Calculate the axes with respect of the camera, doesn't consider angle>180 (use dot()<0 for that)
// (e.g. rotation might be reverse when looking from behind)
zAxis.set(cam.direction).scl(-1);
xAxis.set(zAxis).crs(cam.up);
yAxis.set(xAxis).crs(zAxis);
return true;
}
private static final Vector3 previousPosition = new Vector3();
private static final Quaternion previousRotation = new Quaternion();
/**
* Rotates a transformation matrix around the specified X and Y axis with the specified degrees for each axis.
*/
public static void rotateDice(Matrix4 transform, Vector3 xAxis, float degreesX, Vector3 yAxis, float degreesY) {
transform.getTranslation(previousPosition);
transform.getRotation(previousRotation).nor();
transform.idt();
transform.rotate(xAxis, degreesX);
transform.rotate(yAxis, degreesY);
transform.rotate(previousRotation);
transform.trn(previousPosition);
}
private final static Vector3 tmpV1 = new Vector3();
/**
* Checks which instance is touched given the ray (see {@link Camera#getPickRay(float, float)}). Assumes all instances
* have the same bounding sphere.
* @param out Receives the location in world space closest to the instance
* @param instances The array of instances to check
* @param squaredRadius The squared radius of the bounding sphere for each instance
* @param ray The pick ray (see {@link Camera#getPickRay(float, float)}
* @return The index of the instance within the array that is touched, or negative if none.
*/
public static int getTouchedInstance(Vector3 out, Array<ModelInstance> instances, float squaredRadius, Ray ray) {
int result = -1;
float dist = -1;
for (int i = instances.size - 1; i >= 0; --i) {
instances.get(i).transform.getTranslation(tmpV1);
final float d = raySphereIntersection(tmpV1, squaredRadius, ray.origin, ray.direction);
if (d >= 0f && (dist < 0 || d < dist)) {
out.set(tmpV1);
result = i;
dist = d;
}
}
if (result >= 0)
out.add(ray.origin);
return result;
}
/**
* Checks if the given ray intersect the given sphere. And if so, set the sphereCenter to the point on the ray closest to
* the center of the sphere and returns the distance of the sphere to the origin of the ray. If the sphere is behind
* the origin of the ray or if the ray doesn't intersects the sphere, it will return negative.
* The sphereCenter parameter will always be altered, regardless intersection.
* @param sphereCenter The center of the sphere, will receive the location of point closest to the center of sphere
* relative to the ray origin.
* @param sphereRadiusSquared The squared radius of the sphere
* @param rayOrigin The origin of the ray (see {@link Ray#origin})
* @param rayDirection The direction of the ray, must be normalized (see {@link Ray#direction})
* @return On success, the squared distance from the origin to the closest point to the center of the sphere,
* or negative on no intersection.
*/
public static float raySphereIntersection(Vector3 sphereCenter, float sphereRadiusSquared, Vector3 rayOrigin, Vector3 rayDirection) {
sphereCenter.sub(rayOrigin);
float sx = sphereCenter.x, sy = sphereCenter.y, sz = sphereCenter.z;
final float len = rayDirection.dot(sphereCenter);
if (len <= 0.f)
return -1f;
sphereCenter.set(rayDirection).scl(len);
if (sphereCenter.dst2(sx, sy, sz) > sphereRadiusSquared)
return -1f;
return sphereCenter.len2();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment