org.spout.reactsandbox.Sandbox.java Source code

Java tutorial

Introduction

Here is the source code for org.spout.reactsandbox.Sandbox.java

Source

/*
 * This file is part of ReactSandbox.
 *
 * Copyright (c) 2013 Spout LLC <http://www.spout.org/>
 * ReactSandbox is licensed under the Spout License Version 1.
 *
 * ReactSandbox is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * In addition, 180 days after any changes are published, you can use the
 * software, incorporating those changes, under the terms of the MIT license,
 * as described in the Spout License Version 1.
 *
 * ReactSandbox is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License,
 * the MIT license and the Spout License Version 1 along with this program.
 * If not, see <http://www.gnu.org/licenses/> for the GNU Lesser General Public
 * License and see <http://spout.in/licensev1> for the full license, including
 * the MIT license.
 */
package org.spout.reactsandbox;

import java.io.File;
import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.SystemUtils;
import org.lwjgl.Sys;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.yaml.snakeyaml.Yaml;

import org.spout.math.TrigMath;
import org.spout.physics.body.CollisionBody;
import org.spout.physics.body.ImmobileRigidBody;
import org.spout.physics.body.MobileRigidBody;
import org.spout.physics.body.RigidBody;
import org.spout.physics.body.RigidBodyMaterial;
import org.spout.physics.collision.RayCaster.IntersectedBody;
import org.spout.physics.collision.shape.AABB;
import org.spout.physics.collision.shape.BoxShape;
import org.spout.physics.collision.shape.CollisionShape;
import org.spout.physics.collision.shape.CollisionShape.CollisionShapeType;
import org.spout.physics.collision.shape.ConeShape;
import org.spout.physics.collision.shape.CylinderShape;
import org.spout.physics.collision.shape.SphereShape;
import org.spout.physics.engine.DynamicsWorld;
import org.spout.physics.math.Quaternion;
import org.spout.physics.math.Transform;
import org.spout.physics.math.Vector3;
import org.spout.renderer.Camera;
import org.spout.renderer.GLVersioned.GLVersion;
import org.spout.renderer.data.Color;
import org.spout.renderer.model.Model;

/**
 * The main class of the ReactSandbox.
 */
public class Sandbox {
    // Constants
    public static final int TARGET_FPS = 60;
    private static final float TIMESTEP = 1f / TARGET_FPS;
    private static final RigidBodyMaterial PHYSICS_MATERIAL = RigidBodyMaterial
            .asUnmodifiableMaterial(new RigidBodyMaterial(0.2f, 0.8f));
    public static final float SPOT_CUTOFF = (float) (TrigMath.atan(100 / 50) / 2);
    // Settings
    private static float mouseSensitivity = 0.08f;
    private static float cameraSpeed = 0.2f;
    // Physics objects
    private static DynamicsWorld world;
    private static final Vector3 gravity = new Vector3(0, -9.81f, 0);
    private static final Map<CollisionBody, Model> shapes = new HashMap<>();
    private static final Map<CollisionBody, Model> aabbs = new HashMap<>();
    // Input
    private static boolean mouseGrabbed = true;
    private static float cameraPitch = 0;
    private static float cameraYaw = 0;
    // Selection
    private static CollisionBody selected = null;
    // Rendering
    private static GLVersion glVersion;

    /**
     * Entry point for the application.
     *
     * @param args Unused
     */
    public static void main(String[] args) {
        try {
            deploy();
            loadConfiguration();
            SandboxRenderer.init();
            SandboxRenderer.addDefaultObjects();
            setupPhysics();
            startupLog();
            SandboxRenderer.getCamera().setPosition(new org.spout.math.vector.Vector3(0, 5, 10));
            SandboxRenderer.setLightPosition(new org.spout.math.vector.Vector3(0, 50, 50));
            SandboxRenderer.setLightDirection(
                    new org.spout.math.vector.Vector3(0, -TrigMath.cos(SPOT_CUTOFF), -TrigMath.sin(SPOT_CUTOFF)));
            Mouse.setGrabbed(true);
            SandboxRenderer.startFPSMonitor();
            long lastTime = System.currentTimeMillis();
            while (!Display.isCloseRequested()) {
                final long currentTime = System.currentTimeMillis();
                processInput((currentTime - lastTime) / 1000f);
                lastTime = currentTime;
                world.update();
                handleSelection();
                updateBodies();
                SandboxRenderer.render();
                Display.sync(TARGET_FPS);
            }
            shutdownLog();
            world.stop();
            SandboxRenderer.dispose();
        } catch (Exception ex) {
            ex.printStackTrace();
            final String name = ex.getClass().getSimpleName();
            final String message = ex.getMessage();
            Sys.alert("Error: " + name, message == null || message.trim().equals("") ? name : message);
            System.exit(-1);
        }
    }

    private static ImmobileRigidBody addImmobileBody(CollisionShape shape, float mass, Vector3 position,
            Quaternion orientation) {
        final ImmobileRigidBody body = world.createImmobileRigidBody(new Transform(position, orientation), mass,
                shape);
        addBody(body);
        return body;
    }

    private static MobileRigidBody addMobileBody(CollisionShape shape, float mass, Vector3 position,
            Quaternion orientation) {
        final MobileRigidBody body = world.createMobileRigidBody(new Transform(position, orientation), mass, shape);
        addBody(body);
        return body;
    }

    private static CollisionBody addBody(CollisionBody body) {
        final Transform bodyTransform = body.getTransform();
        final Vector3 bodyPosition = bodyTransform.getPosition();
        final Quaternion bodyOrientation = bodyTransform.getOrientation();
        final AABB aabb = body.getAABB();
        final Model aabbModel = SandboxRenderer.addAABB(SandboxUtil.toMathVector3(bodyPosition),
                SandboxUtil.toMathVector3(Vector3.subtract(aabb.getMax(), aabb.getMin())));
        aabbs.put(body, aabbModel);
        final CollisionShape shape = body.getCollisionShape();
        final Model shapeModel;
        switch (shape.getType()) {
        case BOX:
            final BoxShape box = (BoxShape) shape;
            shapeModel = SandboxRenderer.addBox(SandboxUtil.toMathVector3(bodyPosition),
                    SandboxUtil.toMathQuaternion(bodyOrientation), SandboxUtil.toMathVector3(box.getExtent()));
            break;
        case CONE:
            shapeModel = SandboxRenderer.addDiamond(SandboxUtil.toMathVector3(bodyPosition),
                    SandboxUtil.toMathQuaternion(bodyOrientation));
            break;
        case CYLINDER:
            final CylinderShape cylinder = (CylinderShape) shape;
            shapeModel = SandboxRenderer.addCylinder(SandboxUtil.toMathVector3(bodyPosition),
                    SandboxUtil.toMathQuaternion(bodyOrientation), cylinder.getRadius(), cylinder.getHeight());
            break;
        case SPHERE:
            final SphereShape sphere = (SphereShape) shape;
            shapeModel = SandboxRenderer.addSphere(SandboxUtil.toMathVector3(bodyPosition),
                    SandboxUtil.toMathQuaternion(bodyOrientation), sphere.getRadius());
            break;
        default:
            throw new IllegalArgumentException("Unsupported collision shape: " + shape.getType());
        }
        shapes.put(body, shapeModel);
        return body;
    }

    private static void removeBody(final CollisionBody body) {
        if (body == null) {
            return;
        }
        final Model shapeModel = shapes.remove(body);
        SandboxRenderer.removeModel(shapeModel);
        final Model aabbModel = aabbs.remove(body);
        SandboxRenderer.removeModel(aabbModel);
        if (body instanceof RigidBody) {
            world.destroyRigidBody((RigidBody) body);
        }
    }

    private static void spawnBody(final CollisionShapeType type) {
        CollisionShape shape = null;
        switch (type) {
        case BOX:
            shape = new BoxShape(1, 1, 1);
            break;
        case CONE:
            shape = new ConeShape(1, 2);
            break;
        case CYLINDER:
            shape = new CylinderShape(1, 2);
            break;
        case SPHERE:
            shape = new SphereShape(1);
            break;
        }
        final Camera camera = SandboxRenderer.getCamera();
        addMobileBody(shape, 10, SandboxUtil.toReactVector3(camera.getPosition().add(camera.getForward().mul(5))),
                SandboxUtil.toReactQuaternion(camera.getRotation()));
    }

    private static void updateBodies() {
        for (Entry<CollisionBody, Model> entry : shapes.entrySet()) {
            final CollisionBody body = entry.getKey();
            final Model shape = entry.getValue();
            final Model aabbModel = aabbs.get(body);
            final AABB aabb = body.getAABB();
            final Transform transform = body.getInterpolatedTransform();
            final Vector3 position = transform.getPosition();
            aabbModel.setPosition(SandboxUtil.toMathVector3(position));
            aabbModel.setScale(SandboxUtil.toMathVector3(Vector3.subtract(aabb.getMax(), aabb.getMin())));
            shape.setPosition(SandboxUtil.toMathVector3(position));
            shape.setRotation(SandboxUtil.toMathQuaternion(transform.getOrientation()));
        }
    }

    private static void processInput(float dt) {
        dt /= TIMESTEP;
        final boolean mouseGrabbedBefore = mouseGrabbed;
        while (Keyboard.next()) {
            if (Keyboard.getEventKeyState()) {
                switch (Keyboard.getEventKey()) {
                case Keyboard.KEY_ESCAPE:
                    mouseGrabbed ^= true;
                    break;
                case Keyboard.KEY_F2:
                    SandboxRenderer.saveScreenshot();
                }
            }
        }
        while (Mouse.next()) {
            if (Mouse.getEventButtonState() && mouseGrabbed) {
                switch (Mouse.getEventButton()) {
                case 0: // Left Button
                    spawnBody(CollisionShapeType.CONE);
                    break;
                case 1: // Right Button
                    removeBody(selected);
                    selected = null;
                }
            }
        }
        final Camera camera = SandboxRenderer.getCamera();
        if (Display.isActive()) {
            if (mouseGrabbed != mouseGrabbedBefore) {
                Mouse.setGrabbed(!mouseGrabbedBefore);
            }
            if (mouseGrabbed) {
                final float sensitivity = mouseSensitivity * dt;
                cameraPitch -= Mouse.getDX() * sensitivity;
                cameraPitch %= 360;
                final Quaternion pitch = SandboxUtil.angleAxisToQuaternion(cameraPitch, 0, 1, 0);
                cameraYaw += Mouse.getDY() * sensitivity;
                cameraYaw %= 360;
                final Quaternion yaw = SandboxUtil.angleAxisToQuaternion(cameraYaw, 1, 0, 0);
                camera.setRotation(SandboxUtil.toMathQuaternion(Quaternion.multiply(pitch, yaw)));
            }
        }
        final Vector3 right = SandboxUtil.toReactVector3(camera.getRight());
        final Vector3 up = SandboxUtil.toReactVector3(camera.getUp());
        final Vector3 forward = SandboxUtil.toReactVector3(camera.getForward());
        final Vector3 position = SandboxUtil.toReactVector3(camera.getPosition());
        final float speed = cameraSpeed * dt;
        if (Keyboard.isKeyDown(Keyboard.KEY_W)) {
            position.add(Vector3.multiply(forward, speed));
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_S)) {
            position.add(Vector3.multiply(forward, -speed));
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_A)) {
            position.add(Vector3.multiply(right, speed));
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_D)) {
            position.add(Vector3.multiply(right, -speed));
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_SPACE)) {
            position.add(Vector3.multiply(up, speed));
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) {
            position.add(Vector3.multiply(up, -speed));
        }
        camera.setPosition(SandboxUtil.toMathVector3(position));
    }

    private static void handleSelection() {
        if (selected != null) {
            aabbs.get(selected).getUniforms().getColor("modelColor").set(SandboxRenderer.getAABBColor());
            selected = null;
        }
        final Camera camera = SandboxRenderer.getCamera();
        final IntersectedBody targeted = world.findClosestIntersectingBody(
                SandboxUtil.toReactVector3(camera.getPosition()), SandboxUtil.toReactVector3(camera.getForward()));
        if (targeted != null && targeted.getBody() instanceof RigidBody) {
            selected = targeted.getBody();
            aabbs.get(selected).getUniforms().getColor("modelColor").set(Color.BLUE);
        }
    }

    private static void startupLog() {
        System.out.println("Starting up");
        System.out.println("Render Mode: " + glVersion);
        System.out.println("OpenGL Version: " + GL11.glGetString(GL11.GL_VERSION));
    }

    private static void shutdownLog() {
        System.out.println("Shutting down");
    }

    private static void setupPhysics() {
        world = new DynamicsWorld(gravity, TIMESTEP);
        addMobileBody(new BoxShape(new Vector3(1, 1, 1)), 1, new Vector3(0, 6, 0),
                SandboxUtil.angleAxisToQuaternion(45, 1, 1, 1)).setMaterial(PHYSICS_MATERIAL);
        addMobileBody(new BoxShape(new Vector3(0.28f, 0.28f, 0.28f)), 1, new Vector3(0, 6, 0),
                SandboxUtil.angleAxisToQuaternion(45, 1, 1, 1)).setMaterial(PHYSICS_MATERIAL);
        addMobileBody(new ConeShape(1, 2), 1, new Vector3(0, 9, 0),
                SandboxUtil.angleAxisToQuaternion(89, -1, -1, -1)).setMaterial(PHYSICS_MATERIAL);
        addMobileBody(new CylinderShape(1, 2), 1, new Vector3(0, 12, 0),
                SandboxUtil.angleAxisToQuaternion(-15, 1, -1, 1)).setMaterial(PHYSICS_MATERIAL);
        addMobileBody(new SphereShape(1), 1, new Vector3(0, 15, 0),
                SandboxUtil.angleAxisToQuaternion(32, -1, -1, 1)).setMaterial(PHYSICS_MATERIAL);
        addImmobileBody(new BoxShape(new Vector3(25, 1, 25)), 100, new Vector3(0, 1.8f, 0), Quaternion.identity())
                .setMaterial(PHYSICS_MATERIAL);
        addImmobileBody(new BoxShape(new Vector3(50, 1, 50)), 100, new Vector3(0, 0, 0), Quaternion.identity())
                .setMaterial(PHYSICS_MATERIAL);
        world.start();
    }

    @SuppressWarnings("unchecked")
    private static void loadConfiguration() throws Exception {
        try {
            final Map<String, Object> config = (Map<String, Object>) new Yaml()
                    .load(new FileInputStream("config.yml"));
            final Map<String, Object> inputConfig = (Map<String, Object>) config.get("Input");
            final Map<String, Object> appearanceConfig = (Map<String, Object>) config.get("Appearance");
            mouseSensitivity = ((Number) inputConfig.get("MouseSensitivity")).floatValue();
            cameraSpeed = ((Number) inputConfig.get("CameraSpeed")).floatValue();
            glVersion = GLVersion.valueOf(((String) appearanceConfig.get("GLVersion")).toUpperCase());
            SandboxRenderer.setGLVersion(glVersion);
            SandboxRenderer.setBackgroundColor(parseColor(((String) appearanceConfig.get("BackgroundColor")), 0));
            SandboxRenderer.setAABBColor(parseColor(((String) appearanceConfig.get("AABBColor")), 1));
            SandboxRenderer.setDiamondColor(parseColor(((String) appearanceConfig.get("ConeShapeColor")), 1));
            SandboxRenderer.setSphereColor(parseColor(((String) appearanceConfig.get("SphereShapeColor")), 1));
            SandboxRenderer.setCylinderColor(parseColor(((String) appearanceConfig.get("CylinderShapeColor")), 1));
            SandboxRenderer.setLightAttenuation(((Number) appearanceConfig.get("LightAttenuation")).floatValue());
            SandboxRenderer.setCullBackFaces((Boolean) appearanceConfig.get("CullingEnabled"));
        } catch (Exception ex) {
            throw new IllegalStateException("Malformed config.yml: \"" + ex.getMessage() + "\".", ex);
        }
    }

    private static void deploy() throws Exception {
        final File configFile = new File("config.yml");
        if (!configFile.exists()) {
            FileUtils.copyInputStreamToFile(Sandbox.class.getResourceAsStream("/config.yml"), configFile);
        }
        final String osPath;
        final String[] nativeLibs;
        if (SystemUtils.IS_OS_WINDOWS) {
            nativeLibs = new String[] { "jinput-dx8_64.dll", "jinput-dx8.dll", "jinput-raw_64.dll",
                    "jinput-raw.dll", "jinput-wintab.dll", "lwjgl.dll", "lwjgl64.dll", "OpenAL32.dll",
                    "OpenAL64.dll" };
            osPath = "windows/";
        } else if (SystemUtils.IS_OS_MAC) {
            nativeLibs = new String[] { "libjinput-osx.jnilib", "liblwjgl.jnilib", "openal.dylib" };
            osPath = "mac/";
        } else if (SystemUtils.IS_OS_LINUX) {
            nativeLibs = new String[] { "liblwjgl.so", "liblwjgl64.so", "libopenal.so", "libopenal64.so",
                    "libjinput-linux.so", "libjinput-linux64.so" };
            osPath = "linux/";
        } else {
            throw new IllegalStateException("Could not get lwjgl natives for OS \"" + SystemUtils.OS_NAME + "\".");
        }
        final File nativesDir = new File("natives" + File.separator + osPath);
        nativesDir.mkdirs();
        for (String nativeLib : nativeLibs) {
            final File nativeFile = new File(nativesDir, nativeLib);
            if (!nativeFile.exists()) {
                FileUtils.copyInputStreamToFile(Sandbox.class.getResourceAsStream("/" + nativeLib), nativeFile);
            }
        }
        final String nativesPath = nativesDir.getAbsolutePath();
        System.setProperty("org.lwjgl.librarypath", nativesPath);
        System.setProperty("net.java.games.input.librarypath", nativesPath);
        final File screenshotDir = new File("screenshots");
        if (!screenshotDir.exists()) {
            screenshotDir.mkdir();
        }
    }

    private static Color parseColor(String s, float alpha) {
        final String[] ss = s.split(",");
        return new Color(Float.parseFloat(ss[0].trim()), Float.parseFloat(ss[1].trim()),
                Float.parseFloat(ss[2].trim()), alpha);
    }
}