aboutsummaryrefslogtreecommitdiffstats
path: root/ardor3d-examples
diff options
context:
space:
mode:
Diffstat (limited to 'ardor3d-examples')
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/ArdorBaseApplication.java395
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/ArdorCraftGame.java24
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/CanvasRelayer.java15
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/NativeCanvasRelayer.java35
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/PropertiesDialog.java664
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/PropertiesGameSettings.java818
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/MinecraftMapConverter.java70
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/RegionFile.java391
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/RegionFileCache.java97
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/Tag.java508
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/advanced/AdvancedApplication.java16
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/advanced/AdvancedGame.java213
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/intermediate/IntermediateApplication.java16
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/intermediate/IntermediateGame.java238
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/simple/SimpleApplication.java16
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/simple/SimpleGame.java149
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/RealGame.java475
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/RealGameApplication.java16
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/SelectDialog.java189
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/GeneratorViewer.java334
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/InterpolatedNoiseDataGenerator.java236
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/LayerDataGenerator.java59
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/NiceDataGenerator.java215
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/network/LocalServerConnection.java117
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/network/LocalServerDataHandler.java272
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/player/PlayerWithCollision.java183
-rw-r--r--ardor3d-examples/src/main/java/com/ardor3d/example/craft/player/PlayerWithPhysics.java127
27 files changed, 5888 insertions, 0 deletions
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/ArdorBaseApplication.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/ArdorBaseApplication.java
new file mode 100644
index 0000000..309bbe9
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/ArdorBaseApplication.java
@@ -0,0 +1,395 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.example.craft.base;
+
+import java.awt.EventQueue;
+import java.net.URL;
+import java.util.Stack;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.annotation.MainThread;
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.framework.CanvasRenderer;
+import com.ardor3d.framework.DisplaySettings;
+import com.ardor3d.framework.FrameHandler;
+import com.ardor3d.framework.NativeCanvas;
+import com.ardor3d.framework.Scene;
+import com.ardor3d.framework.Updater;
+import com.ardor3d.framework.jogl.JoglCanvasRenderer;
+import com.ardor3d.framework.jogl.JoglNewtWindow;
+import com.ardor3d.image.util.awt.ScreenShotImageExporter;
+import com.ardor3d.image.util.jogl.JoglImageLoader;
+import com.ardor3d.input.Key;
+import com.ardor3d.input.MouseManager;
+import com.ardor3d.input.PhysicalLayer;
+import com.ardor3d.input.jogl.JoglNewtKeyboardWrapper;
+import com.ardor3d.input.jogl.JoglNewtMouseManager;
+import com.ardor3d.input.jogl.JoglNewtMouseWrapper;
+import com.ardor3d.input.logical.DummyControllerWrapper;
+import com.ardor3d.input.logical.InputTrigger;
+import com.ardor3d.input.logical.KeyPressedCondition;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.input.logical.TriggerAction;
+import com.ardor3d.input.logical.TwoInputStates;
+import com.ardor3d.intersection.PickData;
+import com.ardor3d.intersection.PickResults;
+import com.ardor3d.intersection.PickingUtil;
+import com.ardor3d.intersection.PrimitivePickResults;
+import com.ardor3d.math.Ray3;
+import com.ardor3d.renderer.ContextCapabilities;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.TextureRendererFactory;
+import com.ardor3d.renderer.jogl.JoglTextureRendererProvider;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.WireframeState;
+import com.ardor3d.renderer.state.ZBufferState;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.event.DirtyType;
+import com.ardor3d.scenegraph.hint.CullHint;
+import com.ardor3d.util.Constants;
+import com.ardor3d.util.ContextGarbageCollector;
+import com.ardor3d.util.GameTaskQueue;
+import com.ardor3d.util.GameTaskQueueManager;
+import com.ardor3d.util.ReadOnlyTimer;
+import com.ardor3d.util.Timer;
+import com.ardor3d.util.geom.Debugger;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.ardor3d.util.screen.ScreenExporter;
+import com.ardor3d.util.stat.StatCollector;
+import com.ardorcraft.util.queue.WorkerManager;
+
+public abstract class ArdorBaseApplication implements Runnable, Updater, Scene {
+ private static final Logger logger = Logger.getLogger(ArdorBaseApplication.class.getName());
+
+ private final ArdorCraftGame ardorCraft;
+
+ /** If true (the default) we will call System.exit on end of demo. */
+ public static boolean QUIT_VM_ON_EXIT = true;
+
+ protected final LogicalLayer _logicalLayer = new LogicalLayer();
+
+ protected PhysicalLayer _physicalLayer;
+
+ protected final Timer _timer = new Timer();
+ protected final FrameHandler _frameHandler = new FrameHandler(_timer);
+
+ protected DisplaySettings _settings;
+
+ protected final Node _root = new Node();
+
+ protected WireframeState _wireframeState;
+
+ protected volatile boolean _exit = false;
+
+ protected NativeCanvas _canvas;
+
+ protected ScreenShotImageExporter _screenShotExp = new ScreenShotImageExporter();
+
+ protected boolean _showBounds = false;
+ protected boolean _doShot = false;
+
+ protected MouseManager _mouseManager;
+
+ protected static int _minDepthBits = -1;
+ protected static int _minAlphaBits = -1;
+ protected static int _minStencilBits = -1;
+
+ public ArdorBaseApplication(final ArdorCraftGame ardorCraft) {
+ this.ardorCraft = ardorCraft;
+
+ // Ask for properties
+ final PropertiesGameSettings prefs = getAttributes(
+ new PropertiesGameSettings("ardorSettings.properties", null));
+
+ // Convert to DisplayProperties (XXX: maybe merge these classes?)
+ final DisplaySettings settings = new DisplaySettings(prefs.getWidth(), prefs.getHeight(), prefs.getDepth(),
+ prefs.getFrequency(),
+ // alpha
+ _minAlphaBits != -1 ? _minAlphaBits : prefs.getAlphaBits(),
+ // depth
+ _minDepthBits != -1 ? _minDepthBits : prefs.getDepthBits(),
+ // stencil
+ _minStencilBits != -1 ? _minStencilBits : prefs.getStencilBits(),
+ // samples
+ prefs.getSamples(),
+ // other
+ prefs.isFullscreen(), false);
+
+ _settings = settings;
+
+ // get our framework
+ final JoglCanvasRenderer canvasRenderer = new JoglCanvasRenderer(this);
+ _canvas = new JoglNewtWindow(canvasRenderer, settings);
+ _mouseManager = new JoglNewtMouseManager((JoglNewtWindow) _canvas);
+ _physicalLayer = new PhysicalLayer(new JoglNewtKeyboardWrapper((JoglNewtWindow) _canvas),
+ new JoglNewtMouseWrapper((JoglNewtWindow) _canvas, _mouseManager), new DummyControllerWrapper());
+ TextureRendererFactory.INSTANCE.setProvider(new JoglTextureRendererProvider());
+
+ // _logicalLayer.registerInput(_canvas, _physicalLayer);
+
+ // Register our example as an updater.
+ _frameHandler.addUpdater(this);
+
+ // register our native canvas
+ _frameHandler.addCanvas(_canvas);
+ }
+
+ @Override
+ public void run() {
+ try {
+ _frameHandler.init();
+
+ while (!_exit) {
+ _frameHandler.updateFrame();
+ Thread.yield();
+ }
+ // grab the graphics context so cleanup will work out.
+ final CanvasRenderer cr = _canvas.getCanvasRenderer();
+ cr.makeCurrentContext();
+ quit(_canvas.getCanvasRenderer().getRenderer());
+ cr.releaseCurrentContext();
+ if (QUIT_VM_ON_EXIT) {
+ System.exit(0);
+ }
+ } catch (final Throwable t) {
+ System.err.println("Throwable caught in MainThread - exiting");
+ t.printStackTrace(System.err);
+ }
+ }
+
+ public void exit() {
+ ardorCraft.destroy();
+ _exit = true;
+ }
+
+ @Override
+ @MainThread
+ public void init() {
+ final ContextCapabilities caps = ContextManager.getCurrentContext().getCapabilities();
+ logger.info("Display Vendor: " + caps.getDisplayVendor());
+ logger.info("Display Renderer: " + caps.getDisplayRenderer());
+ logger.info("Display Version: " + caps.getDisplayVersion());
+ logger.info("Shading Language Version: " + caps.getShadingLanguageVersion());
+
+ registerInputTriggers();
+
+ JoglImageLoader.registerLoader();
+
+ /**
+ * Create a ZBuffer to display pixels closest to the camera above farther ones.
+ */
+ final ZBufferState buf = new ZBufferState();
+ buf.setEnabled(true);
+ buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
+ _root.setRenderState(buf);
+
+ _wireframeState = new WireframeState();
+ _wireframeState.setEnabled(false);
+ _root.setRenderState(_wireframeState);
+
+ _root.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ _root.getSceneHints().setCullHint(CullHint.Never);
+
+ final CanvasRelayer canvas = new NativeCanvasRelayer(_canvas);
+ ardorCraft.init(_root, canvas, _logicalLayer, _physicalLayer, _mouseManager);
+
+ _root.updateGeometricState(0);
+ }
+
+ @Override
+ @MainThread
+ public void update(final ReadOnlyTimer timer) {
+ if (_canvas.isClosing()) {
+ exit();
+ }
+
+ /** update stats, if enabled. */
+ if (Constants.stats) {
+ StatCollector.update();
+ }
+
+ updateLogicalLayer(timer);
+
+ // Execute updateQueue item
+ GameTaskQueueManager.getManager(_canvas.getCanvasRenderer().getRenderContext()).getQueue(GameTaskQueue.UPDATE)
+ .execute();
+
+ /** Call simpleUpdate in any derived classes of ArdorBaseApplication. */
+ ardorCraft.update(timer);
+
+ /** Update controllers/render states/transforms/bounds for rootNode. */
+ // _root.updateGeometricState(timer.getTimePerFrame(), true);
+ }
+
+ protected void updateLogicalLayer(final ReadOnlyTimer timer) {
+ _logicalLayer.checkTriggers(timer.getTimePerFrame());
+ }
+
+ @Override
+ @MainThread
+ public boolean renderUnto(final Renderer renderer) {
+ // Execute renderQueue item
+ WorkerManager.getWorker().execute(renderer);
+
+ _root.updateGeometricState(0, true);
+
+ GameTaskQueueManager.getManager(_canvas.getCanvasRenderer().getRenderContext()).getQueue(GameTaskQueue.RENDER)
+ .execute(renderer);
+
+ // Clean up card garbage such as textures, vbos, etc.
+ ContextGarbageCollector.doRuntimeCleanup(renderer);
+
+ /** Draw the rootNode and all its children. */
+ if (!_canvas.isClosing()) {
+ /** Call renderExample in any derived classes. */
+ ardorCraft.render(renderer);
+ if (_showBounds) {
+ Debugger.drawBounds(_root, renderer, true);
+ }
+
+ if (_doShot) {
+ // force any waiting scene elements to be renderer.
+ renderer.renderBuckets();
+ ScreenExporter.exportCurrentScreen(_canvas.getCanvasRenderer().getRenderer(), _screenShotExp);
+ _doShot = false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public PickResults doPick(final Ray3 pickRay) {
+ final PrimitivePickResults pickResults = new PrimitivePickResults();
+ pickResults.setCheckDistance(true);
+ PickingUtil.findPick(_root, pickRay, pickResults);
+ processPicks(pickResults);
+ return pickResults;
+ }
+
+ protected void processPicks(final PrimitivePickResults pickResults) {
+ int i = 0;
+ while (pickResults.getNumber() > 0
+ && pickResults.getPickData(i).getIntersectionRecord().getNumberOfIntersections() == 0
+ && ++i < pickResults.getNumber()) {
+ }
+ if (pickResults.getNumber() > i) {
+ final PickData pick = pickResults.getPickData(i);
+ System.err.println(
+ "picked: " + pick.getTarget() + " at: " + pick.getIntersectionRecord().getIntersectionPoint(0));
+ } else {
+ System.err.println("picked: nothing");
+ }
+ }
+
+ protected void quit(final Renderer renderer) {
+ ContextGarbageCollector.doFinalCleanup(renderer);
+ _canvas.close();
+ }
+
+ protected static PropertiesGameSettings getAttributes(final PropertiesGameSettings settings) {
+ // Always show the dialog in these examples.
+ URL dialogImage = null;
+ final String dflt = settings.getDefaultSettingsWidgetImage();
+ if (dflt != null) {
+ try {
+ dialogImage = ResourceLocatorTool.getClassPathResource(ArdorBaseApplication.class, dflt);
+ } catch (final Exception e) {
+ logger.log(Level.SEVERE, "Resource lookup of '" + dflt + "' failed. Proceeding.");
+ }
+ }
+ if (dialogImage == null) {
+ logger.fine("No dialog image loaded");
+ } else {
+ logger.fine("Using dialog image '" + dialogImage + "'");
+ }
+
+ final URL dialogImageRef = dialogImage;
+ final AtomicReference<PropertiesDialog> dialogRef = new AtomicReference<PropertiesDialog>();
+ final Stack<Runnable> mainThreadTasks = new Stack<Runnable>();
+ try {
+ if (EventQueue.isDispatchThread()) {
+ dialogRef.set(new PropertiesDialog(settings, dialogImageRef, mainThreadTasks));
+ } else {
+ EventQueue.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ dialogRef.set(new PropertiesDialog(settings, dialogImageRef, mainThreadTasks));
+ }
+ });
+ }
+ } catch (final Exception e) {
+ logger.logp(Level.SEVERE, ArdorBaseApplication.class.getClass().toString(),
+ "ArdorBaseApplication.getAttributes(settings)", "Exception", e);
+ return null;
+ }
+
+ PropertiesDialog dialogCheck = dialogRef.get();
+ while (dialogCheck == null || dialogCheck.isVisible()) {
+ try {
+ // check worker queue for work
+ while (!mainThreadTasks.isEmpty()) {
+ mainThreadTasks.pop().run();
+ }
+ // go back to sleep for a while
+ Thread.sleep(50);
+ } catch (final InterruptedException e) {
+ logger.warning("Error waiting for dialog system, using defaults.");
+ }
+
+ dialogCheck = dialogRef.get();
+ }
+
+ if (dialogCheck.isCancelled()) {
+ System.exit(0);
+ }
+ return settings;
+ }
+
+ protected void registerInputTriggers() {
+ _logicalLayer.registerInput(_canvas, _physicalLayer);
+
+ _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.ESCAPE), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ exit();
+ }
+ }));
+
+ _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.T), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ _wireframeState.setEnabled(!_wireframeState.isEnabled());
+ // Either an update or a markDirty is needed here since we did
+ // not touch the affected spatial directly.
+ _root.markDirty(DirtyType.RenderState);
+ }
+ }));
+
+ _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.B), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ _showBounds = !_showBounds;
+ }
+ }));
+
+ _logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F1), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ _doShot = true;
+ }
+ }));
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/ArdorCraftGame.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/ArdorCraftGame.java
new file mode 100644
index 0000000..40063d4
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/ArdorCraftGame.java
@@ -0,0 +1,24 @@
+
+package com.ardor3d.example.craft.base;
+
+import com.ardor3d.input.MouseManager;
+import com.ardor3d.input.PhysicalLayer;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.util.ReadOnlyTimer;
+
+public interface ArdorCraftGame {
+
+ void render(final Renderer renderer);
+
+ void update(final ReadOnlyTimer timer);
+
+ void init(final Node root, final CanvasRelayer canvas, final LogicalLayer logicalLayer,
+ final PhysicalLayer physicalLayer, final MouseManager mouseManager);
+
+ void resize(final int newWidth, final int newHeight);
+
+ void destroy();
+
+} \ No newline at end of file
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/CanvasRelayer.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/CanvasRelayer.java
new file mode 100644
index 0000000..f119167
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/CanvasRelayer.java
@@ -0,0 +1,15 @@
+
+package com.ardor3d.example.craft.base;
+
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.framework.CanvasRenderer;
+
+public interface CanvasRelayer {
+ CanvasRenderer getCanvasRenderer();
+
+ Canvas getCanvas();
+
+ void setTitle(String title);
+
+ void setVSyncEnabled(boolean enabled);
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/NativeCanvasRelayer.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/NativeCanvasRelayer.java
new file mode 100644
index 0000000..2515652
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/NativeCanvasRelayer.java
@@ -0,0 +1,35 @@
+
+package com.ardor3d.example.craft.base;
+
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.framework.CanvasRenderer;
+import com.ardor3d.framework.NativeCanvas;
+
+public class NativeCanvasRelayer implements CanvasRelayer {
+
+ private final NativeCanvas canvas;
+
+ public NativeCanvasRelayer(final NativeCanvas canvas) {
+ this.canvas = canvas;
+ }
+
+ @Override
+ public CanvasRenderer getCanvasRenderer() {
+ return canvas.getCanvasRenderer();
+ }
+
+ @Override
+ public void setTitle(final String title) {
+ canvas.setTitle(title);
+ }
+
+ @Override
+ public Canvas getCanvas() {
+ return canvas;
+ }
+
+ @Override
+ public void setVSyncEnabled(final boolean enabled) {
+ canvas.setVSyncEnabled(enabled);
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/PropertiesDialog.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/PropertiesDialog.java
new file mode 100644
index 0000000..6e34f79
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/PropertiesDialog.java
@@ -0,0 +1,664 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.example.craft.base;
+
+import java.awt.BorderLayout;
+import java.awt.DisplayMode;
+import java.awt.GraphicsEnvironment;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Set;
+import java.util.Stack;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.UIManager;
+
+import com.ardor3d.util.Ardor3dException;
+
+public final class PropertiesDialog extends JDialog {
+ private static final Logger logger = Logger.getLogger(PropertiesDialog.class.getName());
+
+ private static final long serialVersionUID = 1L;
+
+ // connection to properties file.
+ private final PropertiesGameSettings source;
+
+ // Title Image
+ private URL imageFile = null;
+
+ // Array of supported display modes
+ private DisplayMode[] modes = null;
+
+ // Array of windowed resolutions
+ // private final String[] windowedResolutions = { "640 x 480", "800 x 600", "1024 x 768", "1152 x 864" };
+
+ // Array of possible samples
+ private final String[] samples = { "0 samples", "1 samples", "2 samples", "4 samples", "8 samples" };
+
+ // UI components
+ private JCheckBox fullscreenBox = null;
+
+ private JComboBox<String> displayResCombo = null;
+
+ private JComboBox<String> samplesCombo = null;
+
+ private JComboBox<String> colorDepthCombo = null;
+
+ private JComboBox<String> displayFreqCombo = null;
+
+ private JLabel icon = null;
+
+ private boolean cancelled = false;
+
+ private final Stack<Runnable> mainThreadTasks;
+
+ /**
+ * Constructor for the <code>PropertiesDialog</code>. Creates a properties dialog initialized for the primary
+ * display.
+ *
+ * @param source
+ * the <code>GameSettings</code> object to use for working with the properties file.
+ * @param imageFile
+ * the image file to use as the title of the dialog; <code>null</code> will result in to image being
+ * displayed
+ * @throws Ardor3dException
+ * if the source is <code>null</code>
+ */
+ public PropertiesDialog(final PropertiesGameSettings source, final String imageFile) {
+ this(source, imageFile, null);
+ }
+
+ /**
+ * Constructor for the <code>PropertiesDialog</code>. Creates a properties dialog initialized for the primary
+ * display.
+ *
+ * @param source
+ * the <code>GameSettings</code> object to use for working with the properties file.
+ * @param imageFile
+ * the image file to use as the title of the dialog; <code>null</code> will result in to image being
+ * displayed
+ * @throws Ardor3dException
+ * if the source is <code>null</code>
+ */
+ public PropertiesDialog(final PropertiesGameSettings source, final URL imageFile) {
+ this(source, imageFile, null);
+ }
+
+ /**
+ * Constructor for the <code>PropertiesDialog</code>. Creates a properties dialog initialized for the primary
+ * display.
+ *
+ * @param source
+ * the <code>GameSettings</code> object to use for working with the properties file.
+ * @param imageFile
+ * the image file to use as the title of the dialog; <code>null</code> will result in to image being
+ * displayed
+ * @throws Ardor3dException
+ * if the source is <code>null</code>
+ */
+ public PropertiesDialog(final PropertiesGameSettings source, final String imageFile,
+ final Stack<Runnable> mainThreadTasks) {
+ this(source, getURL(imageFile), mainThreadTasks);
+ }
+
+ /**
+ * Constructor for the <code>PropertiesDialog</code>. Creates a properties dialog initialized for the primary
+ * display.
+ *
+ * @param source
+ * the <code>GameSettings</code> object to use for working with the properties file.
+ * @param imageFile
+ * the image file to use as the title of the dialog; <code>null</code> will result in to image being
+ * displayed
+ * @param mainThreadTasks
+ * @throws Ardor3dException
+ * if the source is <code>null</code>
+ */
+ public PropertiesDialog(final PropertiesGameSettings source, final URL imageFile,
+ final Stack<Runnable> mainThreadTasks) {
+ if (null == source) {
+ throw new Ardor3dException("PropertyIO source cannot be null");
+ }
+
+ this.source = source;
+ this.imageFile = imageFile;
+ this.mainThreadTasks = mainThreadTasks;
+
+ final ModesRetriever retrieval = new ModesRetriever();
+ if (mainThreadTasks != null) {
+ mainThreadTasks.add(retrieval);
+ } else {
+ retrieval.run();
+ }
+ modes = retrieval.getModes();
+ Arrays.sort(modes, new DisplayModeSorter());
+
+ createUI();
+ }
+
+ /**
+ * <code>setImage</code> sets the background image of the dialog.
+ *
+ * @param image
+ * <code>String</code> representing the image file.
+ */
+ public void setImage(final String image) {
+ try {
+ final URL file = new URL("file:" + image);
+ setImage(file);
+ // We can safely ignore the exception - it just means that the user
+ // gave us a bogus file
+ } catch (final MalformedURLException e) {
+ }
+ }
+
+ /**
+ * <code>setImage</code> sets the background image of this dialog.
+ *
+ * @param image
+ * <code>URL</code> pointing to the image file.
+ */
+ public void setImage(final URL image) {
+ icon.setIcon(new ImageIcon(image));
+ pack(); // Resize to accomodate the new image
+ center();
+ }
+
+ /**
+ * <code>showDialog</code> sets this dialog as visble, and brings it to the front.
+ */
+ private void showDialog() {
+ setVisible(true);
+ toFront();
+ }
+
+ /**
+ * <code>center</code> places this <code>PropertiesDialog</code> in the center of the screen.
+ */
+ private void center() {
+ int x, y;
+ x = (Toolkit.getDefaultToolkit().getScreenSize().width - getWidth()) / 2;
+ y = (Toolkit.getDefaultToolkit().getScreenSize().height - getHeight()) / 2;
+ this.setLocation(x, y);
+ }
+
+ /**
+ * <code>init</code> creates the components to use the dialog.
+ */
+ private void createUI() {
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (final Exception e) {
+ logger.warning("Could not set native look and feel.");
+ }
+
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(final WindowEvent e) {
+ cancelled = true;
+ dispose();
+ }
+ });
+
+ setTitle("Select Display Settings");
+
+ // The panels...
+ final JPanel mainPanel = new JPanel();
+ final JPanel centerPanel = new JPanel();
+ final JPanel optionsPanel = new JPanel();
+ final JPanel buttonPanel = new JPanel();
+ // The buttons...
+ final JButton ok = new JButton("Ok");
+ final JButton cancel = new JButton("Cancel");
+
+ icon = new JLabel(imageFile != null ? new ImageIcon(imageFile) : null);
+
+ mainPanel.setLayout(new BorderLayout());
+
+ centerPanel.setLayout(new BorderLayout());
+
+ final KeyListener aListener = new KeyAdapter() {
+ @Override
+ public void keyPressed(final KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER) {
+ if (verifyAndSaveCurrentSelection()) {
+ dispose();
+ }
+ }
+ }
+ };
+
+ displayResCombo = setUpResolutionChooser();
+ displayResCombo.addKeyListener(aListener);
+ samplesCombo = setUpSamplesChooser();
+ samplesCombo.addKeyListener(aListener);
+ colorDepthCombo = new JComboBox<>();
+ colorDepthCombo.addKeyListener(aListener);
+ displayFreqCombo = new JComboBox<>();
+ displayFreqCombo.addKeyListener(aListener);
+ fullscreenBox = new JCheckBox("Fullscreen?");
+ fullscreenBox.setSelected(source.isFullscreen());
+ fullscreenBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ updateResolutionChoices();
+ }
+ });
+
+ updateResolutionChoices();
+ displayResCombo.setSelectedItem(source.getWidth() + " x " + source.getHeight());
+
+ samplesCombo.setSelectedItem(source.getSamples() + " samples");
+
+ optionsPanel.add(displayResCombo);
+ optionsPanel.add(colorDepthCombo);
+ optionsPanel.add(displayFreqCombo);
+ optionsPanel.add(samplesCombo);
+ optionsPanel.add(fullscreenBox);
+ // optionsPanel.add(rendererCombo);
+
+ // Set the button action listeners. Cancel disposes without saving, OK
+ // saves.
+ ok.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ if (verifyAndSaveCurrentSelection()) {
+ dispose();
+ }
+ }
+ });
+
+ cancel.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ cancelled = true;
+ dispose();
+ }
+ });
+
+ buttonPanel.add(ok);
+ buttonPanel.add(cancel);
+
+ if (icon != null) {
+ centerPanel.add(icon, BorderLayout.NORTH);
+ }
+ centerPanel.add(optionsPanel, BorderLayout.SOUTH);
+
+ mainPanel.add(centerPanel, BorderLayout.CENTER);
+ mainPanel.add(buttonPanel, BorderLayout.SOUTH);
+
+ getContentPane().add(mainPanel);
+
+ pack();
+ center();
+ showDialog();
+ }
+
+ /**
+ * <code>verifyAndSaveCurrentSelection</code> first verifies that the display mode is valid for this system, and
+ * then saves the current selection as a properties.cfg file.
+ *
+ * @return if the selection is valid
+ */
+ private boolean verifyAndSaveCurrentSelection() {
+ String display = (String) displayResCombo.getSelectedItem();
+ final boolean fullscreen = fullscreenBox.isSelected();
+
+ final int width = Integer.parseInt(display.substring(0, display.indexOf(" x ")));
+ display = display.substring(display.indexOf(" x ") + 3);
+ final int height = Integer.parseInt(display);
+
+ String depthString = (String) colorDepthCombo.getSelectedItem();
+ int depth = 0;
+ if (depthString != null) {
+ depthString = depthString.substring(0, depthString.indexOf(' '));
+ if (depthString.equals("?")) {
+ depth = DisplayMode.BIT_DEPTH_MULTI;
+ } else {
+ depth = Integer.parseInt(depthString);
+ }
+ }
+
+ final String freqString = (String) displayFreqCombo.getSelectedItem();
+ int freq = -1;
+ if (fullscreen) {
+ freq = Integer.parseInt(freqString.substring(0, freqString.indexOf(' ')));
+ }
+
+ final String samplesString = (String) samplesCombo.getSelectedItem();
+ int samples = -1;
+ samples = Integer.parseInt(samplesString.substring(0, samplesString.indexOf(' ')));
+
+ boolean valid = false;
+
+ // test valid display mode when going full screen
+ if (!fullscreen) {
+ valid = true;
+ } else {
+ final ModeValidator validator = new ModeValidator(width, height, depth, freq, samples);
+ if (mainThreadTasks != null) {
+ mainThreadTasks.add(validator);
+ } else {
+ validator.run();
+ }
+
+ valid = validator.isValid();
+ }
+
+ if (valid) {
+ // use the GameSettings class to save it.
+ source.setWidth(width);
+ source.setHeight(height);
+ source.setDepth(depth);
+ source.setFrequency(freq);
+ source.setFullscreen(fullscreen);
+ source.setSamples(samples);
+ try {
+ source.save();
+ } catch (final IOException ioe) {
+ logger.log(Level.WARNING, "Failed to save setting changes", ioe);
+ }
+ } else {
+ showError(this, "Your monitor claims to not support the display mode you've selected.\n"
+ + "The combination of bit depth and refresh rate is not supported.");
+ }
+
+ return valid;
+ }
+
+ /**
+ * <code>setUpChooser</code> retrieves all available display modes and places them in a <code>JComboBox</code>. The
+ * resolution specified by GameSettings is used as the default value.
+ *
+ * @return the combo box of display modes.
+ */
+ private JComboBox<String> setUpResolutionChooser() {
+ final String[] res = getResolutions(modes);
+ final JComboBox<String> resolutionBox = new JComboBox<>(res);
+
+ resolutionBox.setSelectedItem(source.getWidth() + " x " + source.getHeight());
+ resolutionBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ updateDisplayChoices();
+ }
+ });
+
+ return resolutionBox;
+ }
+
+ private JComboBox<String> setUpSamplesChooser() {
+ final JComboBox<String> nameBox = new JComboBox<>(samples);
+ return nameBox;
+ }
+
+ /**
+ * <code>updateDisplayChoices</code> updates the available color depth and display frequency options to match the
+ * currently selected resolution.
+ */
+ private void updateDisplayChoices() {
+ if (!fullscreenBox.isSelected()) {
+ // don't run this function when changing windowed settings
+ return;
+ }
+ final String resolution = (String) displayResCombo.getSelectedItem();
+ String colorDepth = (String) colorDepthCombo.getSelectedItem();
+ if (colorDepth == null) {
+ colorDepth = source.getDepth() != DisplayMode.BIT_DEPTH_MULTI ? source.getDepth() + " bpp" : "? bpp";
+ }
+ String displayFreq = (String) displayFreqCombo.getSelectedItem();
+ if (displayFreq == null) {
+ displayFreq = source.getFrequency() + " Hz";
+ }
+
+ // grab available depths
+ final String[] depths = getDepths(resolution, modes);
+ colorDepthCombo.setModel(new DefaultComboBoxModel<>(depths));
+ colorDepthCombo.setSelectedItem(colorDepth);
+ // grab available frequencies
+ final String[] freqs = getFrequencies(resolution, modes);
+ displayFreqCombo.setModel(new DefaultComboBoxModel<>(freqs));
+ // Try to reset freq
+ displayFreqCombo.setSelectedItem(displayFreq);
+ }
+
+ /**
+ * <code>updateResolutionChoices</code> updates the available resolutions list to match the currently selected
+ * window mode (fullscreen or windowed). It then sets up a list of standard options (if windowed) or calls
+ * <code>updateDisplayChoices</code> (if fullscreen).
+ */
+ private void updateResolutionChoices() {
+ if (!fullscreenBox.isSelected()) {
+ displayResCombo.setModel(new DefaultComboBoxModel<>(getResolutions(modes)));
+ // displayResCombo.setModel(new DefaultComboBoxModel(windowedResolutions));
+ colorDepthCombo.setModel(new DefaultComboBoxModel<>(new String[] { "24 bpp", "16 bpp" }));
+ displayFreqCombo.setModel(new DefaultComboBoxModel<>(new String[] { "n/a" }));
+ displayFreqCombo.setEnabled(false);
+ } else {
+ displayResCombo.setModel(new DefaultComboBoxModel<>(getResolutions(modes)));
+ displayFreqCombo.setEnabled(true);
+ updateDisplayChoices();
+ }
+ pack();
+ }
+
+ //
+ // Utility methods
+ //
+
+ /**
+ * Utility method for converting a String denoting a file into a URL.
+ *
+ * @return a URL pointing to the file or null
+ */
+ private static URL getURL(final String file) {
+ URL url = null;
+ try {
+ url = new URL("file:" + file);
+ } catch (final MalformedURLException e) {
+ }
+ return url;
+ }
+
+ private static void showError(final java.awt.Component parent, final String message) {
+ JOptionPane.showMessageDialog(parent, message, "Error", JOptionPane.ERROR_MESSAGE);
+ }
+
+ /**
+ * Returns every unique resolution from an array of <code>DisplayMode</code>s.
+ */
+ private static String[] getResolutions(final DisplayMode[] modes) {
+ final List<String> resolutions = new ArrayList<>(modes.length);
+ for (int i = 0; i < modes.length; i++) {
+ final String res = modes[i].getWidth() + " x " + modes[i].getHeight();
+ if (!resolutions.contains(res)) {
+ resolutions.add(res);
+ }
+ }
+
+ final String[] res = new String[resolutions.size()];
+ resolutions.toArray(res);
+ return res;
+ }
+
+ /**
+ * Returns every possible bit depth for the given resolution.
+ */
+ private static String[] getDepths(final String resolution, final DisplayMode[] modes) {
+ final Set<String> depths = new TreeSet<>(new Comparator<String>() {
+ @Override
+ public int compare(final String o1, final String o2) {
+ // reverse order
+ return -o1.compareTo(o2);
+ }
+ });
+ for (int i = 0; i < modes.length; i++) {
+ // Filter out modes with bit depths that we don't care about.
+ if (modes[i].getBitDepth() < 16 && modes[i].getBitDepth() != DisplayMode.BIT_DEPTH_MULTI) {
+ continue;
+ }
+
+ final String res = modes[i].getWidth() + " x " + modes[i].getHeight();
+ final String depth = modes[i].getBitDepth() != DisplayMode.BIT_DEPTH_MULTI ? modes[i].getBitDepth() + " bpp"
+ : "? bpp";
+ if (res.equals(resolution) && !depths.contains(depth)) {
+ depths.add(depth);
+ }
+ }
+
+ final String[] res = new String[depths.size()];
+ depths.toArray(res);
+ return res;
+ }
+
+ /**
+ * Returns every possible refresh rate for the given resolution.
+ */
+ private static String[] getFrequencies(final String resolution, final DisplayMode[] modes) {
+ final List<String> freqs = new ArrayList<>(4);
+ for (int i = 0; i < modes.length; i++) {
+ final String res = modes[i].getWidth() + " x " + modes[i].getHeight();
+ final String freq = modes[i].getRefreshRate() + " Hz";
+ if (res.equals(resolution) && !freqs.contains(freq)) {
+ freqs.add(freq);
+ }
+ }
+
+ final String[] res = new String[freqs.size()];
+ freqs.toArray(res);
+ return res;
+ }
+
+ /**
+ * Utility class for sorting <code>DisplayMode</code>s. Sorts by resolution, then bit depth, and then finally
+ * refresh rate.
+ */
+ private static class DisplayModeSorter implements Comparator<DisplayMode> {
+ /**
+ * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+ */
+ @Override
+ public int compare(final DisplayMode a, final DisplayMode b) {
+ // Width
+ if (a.getWidth() != b.getWidth()) {
+ return a.getWidth() > b.getWidth() ? 1 : -1;
+ }
+ // Height
+ if (a.getHeight() != b.getHeight()) {
+ return a.getHeight() > b.getHeight() ? 1 : -1;
+ }
+ // Bit depth
+ if (a.getBitDepth() != b.getBitDepth()) {
+ return a.getBitDepth() > b.getBitDepth() ? 1 : -1;
+ }
+ // Refresh rate
+ if (a.getRefreshRate() != b.getRefreshRate()) {
+ return a.getRefreshRate() > b.getRefreshRate() ? 1 : -1;
+ }
+ // All fields are equal
+ return 0;
+ }
+ }
+
+ /**
+ * @return Returns true if this dialog was cancelled
+ */
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ private static class ModeValidator implements Runnable {
+
+ boolean ready = false, valid = true;
+
+ @SuppressWarnings("unused")
+ int width, height, depth, freq, samples;
+
+ ModeValidator(final int width, final int height, final int depth, final int freq, final int samples) {
+ this.width = width;
+ this.height = height;
+ this.depth = depth;
+ this.freq = freq;
+ this.samples = samples;
+ }
+
+ @Override
+ public void run() {
+ // FIXME the mode should be validated here
+ ready = true;
+ }
+
+ public boolean isValid() {
+ while (!ready) {
+ try {
+ Thread.sleep(10);
+ } catch (final Exception e) {
+ }
+ }
+ return valid;
+ }
+ }
+
+ private static class ModesRetriever implements Runnable {
+
+ boolean ready = false;
+ DisplayMode[] modes = null;
+
+ ModesRetriever() {
+ }
+
+ @Override
+ public void run() {
+ try {
+ modes = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayModes();
+ } catch (final Exception e) {
+ logger.logp(Level.SEVERE, this.getClass().toString(), "PropertiesDialog(GameSettings, URL)",
+ "Exception", e);
+ return;
+ }
+ ready = true;
+ }
+
+ public DisplayMode[] getModes() {
+ while (!ready) {
+ try {
+ Thread.sleep(10);
+ } catch (final Exception e) {
+ }
+ }
+ return modes;
+ }
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/PropertiesGameSettings.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/PropertiesGameSettings.java
new file mode 100644
index 0000000..f628624
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/base/PropertiesGameSettings.java
@@ -0,0 +1,818 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.example.craft.base;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.ardor3d.util.Ardor3dException;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+
+/**
+ * <code>PropertiesGameSettings</code> handles loading and saving a properties file that defines the display settings. A
+ * property file is identified during creation of the object. The properties file should have the following format:
+ *
+ * <PRE>
+ * &lt;CODE&gt;
+ * FREQ=60
+ * WIDTH=1280
+ * HEIGHT=1024
+ * DEPTH=32
+ * FULLSCREEN=false
+ * &lt;/CODE&gt;
+ * </PRE>
+ */
+public class PropertiesGameSettings {
+ private static final Logger logger = Logger.getLogger(PropertiesGameSettings.class.getName());
+
+ /**
+ * The default width, used if there is a problem with the properties file.
+ */
+ static int DEFAULT_WIDTH = 800;
+ /**
+ * The default height, used if there is a problem with the properties file.
+ */
+ static int DEFAULT_HEIGHT = 600;
+ /**
+ * The default depth, used if there is a problem with the properties file.
+ */
+ static int DEFAULT_DEPTH = 16;
+ /**
+ * The default frequency, used if there is a problem with the properties file.
+ */
+ static int DEFAULT_FREQUENCY = 60;
+ /**
+ * The default fullscreen flag, used if there is a problem with the properties file.
+ */
+ static boolean DEFAULT_FULLSCREEN = false;
+
+ static boolean DEFAULT_VERTICAL_SYNC = true;
+ static int DEFAULT_DEPTH_BITS = 8;
+ static int DEFAULT_ALPHA_BITS = 0;
+ static int DEFAULT_STENCIL_BITS = 0;
+ static int DEFAULT_SAMPLES = 0;
+ static boolean DEFAULT_MUSIC = true;
+ static boolean DEFAULT_SFX = true;
+ static int DEFAULT_FRAMERATE = -1;
+
+ protected boolean isNew = true;
+
+ // These are all objects so it is very clear when they have been
+ // explicitly set.
+ protected static Integer defaultWidth = null;
+ protected static Integer defaultHeight = null;
+ protected static Integer defaultDepth = null;
+ protected static Integer defaultFrequency = null;
+ protected static Boolean defaultFullscreen = null;
+ protected static Boolean defaultVerticalSync = null;
+ protected static Integer defaultDepthBits = null;
+ protected static Integer defaultAlphaBits = null;
+ protected static Integer defaultStencilBits = null;
+ protected static Integer defaultSamples = null;
+ protected static Boolean defaultMusic = null;
+ protected static Boolean defaultSFX = null;
+ protected static Integer defaultFramerate = null;
+ protected static String defaultSettingsWidgetImage = null;
+ private static boolean defaultsAssigned = false;
+
+ // property object
+ private final Properties prop;
+
+ // the file that contains our properties.
+ private final String filename;
+
+ private static boolean dfltsInitted = false;
+
+ /**
+ * Constructor creates the <code>PropertiesGameSettings</code> object for use.
+ *
+ * @param personalFilename
+ * the properties file to use, read from filesystem. Must not be null.
+ * @param dfltsFilename
+ * the properties file to use, read from CLASSPATH. Null to not seek any runtime defaults file.
+ * @throws Ardor3dException
+ * if the personalFilename is null.
+ */
+ public PropertiesGameSettings(final String personalFilename, final String dfltsFilename) {
+ if (null == personalFilename) {
+ throw new Ardor3dException("Must give a valid filename");
+ }
+ if (!dfltsInitted) {
+ dfltsInitted = true;
+ // default* setting values are static, therefore, regardless of
+ // how many GameSettings we instantiate, the defaults are
+ // assigned only once.
+ assignDefaults(dfltsFilename);
+ }
+
+ filename = personalFilename;
+ isNew = !new File(filename).isFile();
+ prop = new Properties();
+ load();
+ }
+
+ public void clear() {
+ prop.clear();
+ }
+
+ /**
+ * <code>get</code> takes an arbitrary string as a key and returns any value associated with it, null if none.
+ *
+ * @param key
+ * the key to use for data retrieval.
+ * @return the string associated with the key, null if none.
+ */
+ public String get(final String key) {
+ return prop.getProperty(key);
+ }
+
+ public String get(final String name, final String defaultValue) {
+ final String value = get(name);
+ return value == null ? defaultValue : value;
+ }
+
+ /**
+ * If the properties file does not contain the setting or was not read properly, the default value is returned.
+ *
+ * @throws InternalError
+ * in all cases
+ */
+ public int getAlphaBits() {
+ final String s = prop.getProperty("ALPHA_BITS");
+ return s == null ? defaultAlphaBits : Integer.parseInt(s);
+ }
+
+ public boolean getBoolean(final String name, final boolean defaultValue) {
+ final String stringValue = get(name);
+ return stringValue == null ? defaultValue : Boolean.parseBoolean(stringValue);
+ }
+
+ public byte[] getByteArray(final String name, final byte[] defaultValue) {
+ final String stringValue = get(name);
+ return stringValue == null ? defaultValue : stringValue.getBytes();
+ }
+
+ /**
+ * This is only getting the "default" value, which may not be changed by end-users.
+ */
+ public String getDefaultSettingsWidgetImage() {
+ return defaultSettingsWidgetImage;
+ }
+
+ /**
+ * <code>getDepth</code> returns the depth as read from the properties file. If the properties file does not contain
+ * depth or was not read properly, the default depth is returned.
+ *
+ * @return the depth determined by the properties file, or the default.
+ */
+ public int getDepth() {
+ final String d = prop.getProperty("DEPTH");
+ if (null == d) {
+ return defaultDepth;
+ }
+
+ return Integer.parseInt(d);
+ }
+
+ /**
+ * If the properties file does not contain the setting or was not read properly, the default value is returned.
+ *
+ * @throws InternalError
+ * in all cases
+ */
+ public int getDepthBits() {
+ final String s = prop.getProperty("DEPTH_BITS");
+ return s == null ? defaultDepthBits : Integer.parseInt(s);
+ }
+
+ public double getDouble(final String name, final double defaultValue) {
+ final String stringValue = get(name);
+ return stringValue == null ? defaultValue : Double.parseDouble(stringValue);
+ }
+
+ public float getFloat(final String name, final float defaultValue) {
+ final String stringValue = get(name);
+ return stringValue == null ? defaultValue : Float.parseFloat(stringValue);
+ }
+
+ /**
+ * If the properties file does not contain the setting or was not read properly, the default value is returned.
+ *
+ * @throws InternalError
+ * in all cases
+ */
+ public int getFramerate() {
+ final String s = prop.getProperty("FRAMERATE");
+ return s == null ? defaultFramerate : Integer.parseInt(s);
+ }
+
+ /**
+ * <code>getFrequency</code> returns the frequency of the monitor as read from the properties file. If the
+ * properties file does not contain frequency or was not read properly the default frequency is returned.
+ *
+ * @return the frequency determined by the properties file, or the default.
+ */
+ public int getFrequency() {
+ final String f = prop.getProperty("FREQ");
+ if (null == f) {
+ return defaultFrequency;
+ }
+
+ return Integer.parseInt(f);
+ }
+
+ /**
+ * Legacy method.
+ *
+ * @deprecated Use method isFullscreen instead.
+ * @see #isFullscreen()
+ */
+ @Deprecated
+ public boolean getFullscreen() {
+ return isFullscreen();
+ }
+
+ /**
+ * <code>getHeight</code> returns the height as read from the properties file. If the properties file does not
+ * contain height or was not read properly, the default height is returned.
+ *
+ * @return the height determined by the properties file, or the default.
+ */
+ public int getHeight() {
+ final String h = prop.getProperty("HEIGHT");
+ if (null == h) {
+ return defaultHeight;
+ }
+
+ return Integer.parseInt(h);
+ }
+
+ public int getInt(final String name, final int defaultValue) {
+ final String stringValue = get(name);
+ return stringValue == null ? defaultValue : Integer.parseInt(stringValue);
+ }
+
+ public long getLong(final String name, final long defaultValue) {
+ final String stringValue = get(name);
+ return stringValue == null ? defaultValue : Long.parseLong(stringValue);
+ }
+
+ public Object getObject(final String name, final Object defaultValue) {
+ final String stringValue = get(name);
+ return stringValue == null ? defaultValue : stringValue;
+ }
+
+ /**
+ * If the properties file does not contain the setting or was not read properly, the default value is returned.
+ *
+ * @throws InternalError
+ * in all cases
+ */
+ public int getSamples() {
+ final String s = prop.getProperty("SAMPLES");
+ return s == null ? defaultSamples : Integer.parseInt(s);
+ }
+
+ /**
+ * If the properties file does not contain the setting or was not read properly, the default value is returned.
+ *
+ * @throws InternalError
+ * in all cases
+ */
+ public int getStencilBits() {
+ final String s = prop.getProperty("STENCIL_BITS");
+ return s == null ? defaultStencilBits : Integer.parseInt(s);
+ }
+
+ /**
+ * <code>getWidth</code> returns the width as read from the properties file. If the properties file does not contain
+ * width or was not read properly, the default width is returned.
+ *
+ * @return the width determined by the properties file, or the default.
+ */
+ public int getWidth() {
+ final String w = prop.getProperty("WIDTH");
+ if (null == w) {
+ return defaultWidth;
+ }
+
+ return Integer.parseInt(w);
+ }
+
+ /**
+ * <code>isFullscreen</code> returns the fullscreen flag as read from the properties file. If the properties file
+ * does not contain the fullscreen flag or was not read properly, the default value is returned.
+ *
+ * @return the fullscreen flag determined by the properties file, or the default.
+ */
+ public boolean isFullscreen() {
+ final String f = prop.getProperty("FULLSCREEN");
+ if (null == f) {
+ return defaultFullscreen;
+ }
+
+ return Boolean.valueOf(prop.getProperty("FULLSCREEN"));
+ }
+
+ /**
+ * If the properties file does not contain the setting or was not read properly, the default value is returned.
+ *
+ * @throws InternalError
+ * in all cases
+ */
+ public boolean isMusic() {
+ final String s = prop.getProperty("MUSIC");
+ return s == null ? defaultMusic : Boolean.parseBoolean(s);
+ }
+
+ public boolean isNew() {
+ return isNew;
+ }
+
+ /**
+ * If the properties file does not contain the setting or was not read properly, the default value is returned.
+ *
+ * @throws InternalError
+ * in all cases
+ */
+ public boolean isSFX() {
+ final String s = prop.getProperty("SFX");
+ return s == null ? defaultSFX : Boolean.parseBoolean(s);
+ }
+
+ /**
+ * If the properties file does not contain the setting or was not read properly, the default value is returned.
+ *
+ * @throws InternalError
+ * in all cases
+ */
+ public boolean isVerticalSync() {
+ final String s = prop.getProperty("VERTICAL_SYNC");
+ return s == null ? defaultVerticalSync : Boolean.parseBoolean(s);
+ }
+
+ /**
+ * <code>load</code> attempts to load the properties file defined during instantiation and put all properties in the
+ * table. If there is a problem loading or reading the file, false is returned. If all goes well, true is returned.
+ *
+ * @return the success of the load, true indicated success and false indicates failure.
+ */
+ public boolean load() {
+ FileInputStream fin = null;
+ try {
+ fin = new FileInputStream(filename);
+ } catch (final FileNotFoundException e) {
+ logger.warning("Could not load properties. Creating a new one.");
+ return false;
+ }
+
+ try {
+ prop.load(fin);
+ fin.close();
+ } catch (final IOException e) {
+ logger.warning("Could not load properties. Creating a new one.");
+ return false;
+ }
+
+ // confirm that the properties file has all the data we need.
+ if (null == prop.getProperty("WIDTH") || null == prop.getProperty("HEIGHT") || null == prop.getProperty("DEPTH")
+ || null == prop.getProperty("FULLSCREEN")) {
+ logger.warning("Properties file not complete.");
+ return false;
+ }
+
+ logger.finer("Read properties");
+ return true;
+ }
+
+ /**
+ * Removes specified property, if present.
+ */
+ public void remove(final String name) {
+ prop.remove(name);
+ }
+
+ /**
+ * Persists current property mappings to designated file, overwriting if file already present.
+ *
+ * @throws IOException
+ * for I/O failures
+ */
+ public void save() throws IOException {
+ final FileOutputStream fout = new FileOutputStream(filename);
+ prop.store(fout, "Game Settings written by " + getClass().getName() + " at " + new java.util.Date());
+
+ fout.close();
+ logger.finer("Saved properties");
+ }
+
+ /**
+ * <code>save(int, int, int, int, boolean, String)</code> overwrites the properties file with the given parameters.
+ *
+ * @param width
+ * the width of the resolution.
+ * @param height
+ * the height of the resolution.
+ * @param depth
+ * the bits per pixel.
+ * @param freq
+ * the frequency of the monitor.
+ * @param fullscreen
+ * use fullscreen or not.
+ * @deprecated
+ * @return true if save was successful, false otherwise.
+ */
+ @Deprecated
+ public boolean save(final int width, final int height, final int depth, final int freq, final boolean fullscreen) {
+
+ prop.clear();
+ setWidth(width);
+ setHeight(height);
+ setDepth(depth);
+ setFrequency(freq);
+ setFullscreen(fullscreen);
+
+ try {
+ save();
+ } catch (final IOException e) {
+ logger.warning("Could not save properties: " + e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Sets a property.
+ */
+ public void set(final String name, final String value) {
+ prop.setProperty(name, value);
+ }
+
+ /**
+ * @throws InternalError
+ * in all cases
+ */
+ public void setAlphaBits(final int alphaBits) {
+ setInt("ALPHA_BITS", alphaBits);
+ }
+
+ /**
+ * @see #set(String, String)
+ * @throws RuntimeSetting
+ * for IO failure
+ */
+ public void setBoolean(final String name, final boolean value) {
+ set(name, Boolean.toString(value));
+ }
+
+ /**
+ * @see #set(String, String)
+ * @throws RuntimeSetting
+ * for IO failure
+ */
+ public void setByteArray(final String name, final byte[] value) {
+ set(name, new String(value));
+ }
+
+ public void setDepth(final int depth) {
+ setInt("DEPTH", depth);
+ }
+
+ /**
+ * @throws InternalError
+ * in all cases
+ */
+ public void setDepthBits(final int depthBits) {
+ setInt("DEPTH_BITS", depthBits);
+ }
+
+ /**
+ * @see #set(String, String)
+ * @throws RuntimeSetting
+ * for IO failure
+ */
+ public void setDouble(final String name, final double value) {
+ set(name, Double.toString(value));
+ }
+
+ /**
+ * @see #set(String, String)
+ * @throws RuntimeSetting
+ * for IO failure
+ */
+ public void setFloat(final String name, final float value) {
+ set(name, Float.toString(value));
+ }
+
+ /**
+ * @throws InternalError
+ * in all cases
+ */
+ public void setFramerate(final int framerate) {
+ setInt("FRAMERATE", framerate);
+ }
+
+ public void setFrequency(final int freq) {
+ setInt("FREQ", freq);
+ }
+
+ public void setFullscreen(final boolean fullscreen) {
+ setBoolean("FULLSCREEN", fullscreen);
+ }
+
+ public void setHeight(final int height) {
+ setInt("HEIGHT", height);
+ }
+
+ /**
+ * @see #set(String, String)
+ * @throws RuntimeSetting
+ * for IO failure
+ */
+ public void setInt(final String name, final int value) {
+ set(name, Integer.toString(value));
+ }
+
+ public void setIsNew(final boolean isNew) {
+ this.isNew = isNew;
+ }
+
+ /**
+ * @see #set(String, String)
+ * @throws RuntimeSetting
+ * for IO failure
+ */
+ public void setLong(final String name, final long value) {
+ set(name, Long.toString(value));
+ }
+
+ /**
+ * @throws InternalError
+ * in all cases
+ */
+ public void setMusic(final boolean music) {
+ setBoolean("MUSIC", music);
+ }
+
+ /**
+ * Not implemented. Properties can not store an arbitrary Object in human-readable format. Use set(String, String)
+ * instead.
+ *
+ * @see #set(String, String)
+ * @throws InternalError
+ * in all cases
+ */
+ public void setObject(final String name, final Object value) {
+ throw new InternalError(getClass().getName() + " Can't store arbitrary objects. "
+ + "If there is a toString() method for your Object, and it is " + "Properties-compatible, use "
+ + getClass().getName() + ".set(String, String).");
+ }
+
+ /**
+ * @throws InternalError
+ * in all cases
+ */
+ public void setSamples(final int samples) {
+ setInt("SAMPLES", samples);
+ }
+
+ /**
+ * @throws InternalError
+ * in all cases
+ */
+ public void setSFX(final boolean sfx) {
+ setBoolean("SFX", sfx);
+ }
+
+ /**
+ * @throws InternalError
+ * in all cases
+ */
+ public void setStencilBits(final int stencilBits) {
+ setInt("STENCIL_BITS", stencilBits);
+ }
+
+ /**
+ * @throws InternalError
+ * in all cases
+ */
+ public void setVerticalSync(final boolean verticalSync) {
+ setBoolean("VERTICAL_SYNC", verticalSync);
+ }
+
+ public void setWidth(final int width) {
+ setInt("WIDTH", width);
+ }
+
+ /**
+ * save() method which throws only a RuntimeExceptin.
+ *
+ * @throws RuntimeSetting
+ * for IO failure
+ * @see #save()
+ */
+ public void wrappedSave() {
+ try {
+ save();
+ } catch (final IOException ioe) {
+ logger.log(Level.WARNING, "Failed to persist properties", ioe);
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ /**
+ * Sets default* static variables according to GameSettings.DEFAULT_* values and an optional .properties file. Note
+ * that we are talking about <b>defaults</b> here, not user-specific settings.
+ * <P/>
+ * This method should be called once the game name is known to the subclass. To override any default with your
+ * subclass (as opposed to by using a .properties file), just set the static variable before or after calling this
+ * method (before or after depends on the precedence you want among programmatic and declarative DEFAULT_*, default*
+ * settings).
+ * <P/>
+ * Add new setting names by making your own method which does its own thing and calls
+ * AbstractGameSettings.assignDefaults(propfilename).
+ * <P/>
+ * Property file paths are relative to CLASSPATH element roots.
+ *
+ * @param propFileName
+ * Properties file read as CLASSPATH resource. If you give null, no properties file will be loaded.
+ */
+ protected static void assignDefaults(final String propFileName) {
+ if (defaultsAssigned) {
+ logger.fine("Skipping repeat invocation of assignDefaults()");
+ return;
+ }
+ logger.fine("Initializing static default* setting variables");
+
+ // hansen.playground.logging.LoggerInformation.getInfo();
+ // System.exit(0);
+
+ defaultsAssigned = true;
+ if (defaultWidth == null) {
+ defaultWidth = Integer.valueOf(DEFAULT_WIDTH);
+ }
+ if (defaultHeight == null) {
+ defaultHeight = Integer.valueOf(DEFAULT_HEIGHT);
+ }
+ if (defaultDepth == null) {
+ defaultDepth = Integer.valueOf(DEFAULT_DEPTH);
+ }
+ if (defaultFrequency == null) {
+ defaultFrequency = Integer.valueOf(DEFAULT_FREQUENCY);
+ }
+ if (defaultFullscreen == null) {
+ defaultFullscreen = Boolean.valueOf(DEFAULT_FULLSCREEN);
+ }
+ if (defaultVerticalSync == null) {
+ defaultVerticalSync = Boolean.valueOf(DEFAULT_VERTICAL_SYNC);
+ }
+ if (defaultDepthBits == null) {
+ defaultDepthBits = Integer.valueOf(DEFAULT_DEPTH_BITS);
+ }
+ if (defaultAlphaBits == null) {
+ defaultAlphaBits = Integer.valueOf(DEFAULT_ALPHA_BITS);
+ }
+ if (defaultStencilBits == null) {
+ defaultStencilBits = Integer.valueOf(DEFAULT_STENCIL_BITS);
+ }
+ if (defaultSamples == null) {
+ defaultSamples = Integer.valueOf(DEFAULT_SAMPLES);
+ }
+ if (defaultMusic == null) {
+ defaultMusic = Boolean.valueOf(DEFAULT_MUSIC);
+ }
+ if (defaultSFX == null) {
+ defaultSFX = Boolean.valueOf(DEFAULT_SFX);
+ }
+ if (defaultFramerate == null) {
+ defaultFramerate = Integer.valueOf(DEFAULT_FRAMERATE);
+ }
+ InputStream istream = null;
+ if (propFileName != null) {
+ istream = ResourceLocatorTool.getClassPathResourceAsStream(PropertiesGameSettings.class, propFileName);
+ }
+ if (istream == null) {
+ logger.fine("No customization properties file found");
+ return;
+ }
+ logger.fine("Customizing defaults according to '" + propFileName + "'");
+ final Properties p = new Properties();
+ try {
+ p.load(istream);
+ } catch (final IOException ioe) {
+ logger.log(Level.WARNING,
+ "Failed to load customizations from '" + propFileName + "'. Continuing without customizations.",
+ ioe);
+ return;
+ }
+ Integer i;
+ String s;
+ Boolean b;
+ i = loadInteger("DEFAULT_WIDTH", p);
+ if (i != null) {
+ defaultWidth = i.intValue();
+ }
+ i = loadInteger("DEFAULT_HEIGHT", p);
+ if (i != null) {
+ defaultHeight = i.intValue();
+ }
+ i = loadInteger("DEFAULT_DEPTH", p);
+ if (i != null) {
+ defaultDepth = i.intValue();
+ }
+ i = loadInteger("DEFAULT_FREQUENCY", p);
+ if (i != null) {
+ defaultFrequency = i.intValue();
+ }
+ b = loadBoolean("DEFAULT_FULLSCREEN", p);
+ if (b != null) {
+ defaultFullscreen = b.booleanValue();
+ }
+ b = loadBoolean("DEFAULT_VERTICAL_SYNC", p);
+ if (b != null) {
+ defaultVerticalSync = b.booleanValue();
+ }
+ i = loadInteger("DEFAULT_DEPTH_BITS", p);
+ if (i != null) {
+ defaultDepthBits = i.intValue();
+ }
+ i = loadInteger("DEFAULT_ALPHA_BITS", p);
+ if (i != null) {
+ defaultAlphaBits = i.intValue();
+ }
+ i = loadInteger("DEFAULT_STENCIL_BITS", p);
+ if (i != null) {
+ defaultStencilBits = i.intValue();
+ }
+ i = loadInteger("DEFAULT_SAMPLES", p);
+ if (i != null) {
+ defaultSamples = i.intValue();
+ }
+ b = loadBoolean("DEFAULT_MUSIC", p);
+ if (b != null) {
+ defaultMusic = b.booleanValue();
+ }
+ b = loadBoolean("DEFAULT_SFX", p);
+ if (b != null) {
+ defaultSFX = b.booleanValue();
+ }
+ i = loadInteger("DEFAULT_FRAMERATE", p);
+ if (i != null) {
+ defaultFramerate = i.intValue();
+ }
+ s = p.getProperty("SETTINGS_WIDGET_IMAGE");
+ if (s != null) {
+ defaultSettingsWidgetImage = s;
+ }
+ }
+
+ public static Boolean loadBoolean(final String name, final Properties props) {
+ final String s = props.getProperty(name);
+ if (s == null) {
+ return null;
+ }
+ return Boolean.valueOf(s);
+ }
+
+ public static Integer loadInteger(final String name, final Properties props) {
+ final String s = props.getProperty(name);
+ if (s == null) {
+ return null;
+ }
+ try {
+ return Integer.valueOf(s);
+ } catch (final NumberFormatException nfe) {
+ }
+ logger.warning("Malformatted value in game properties file: " + s);
+ return null;
+ }
+
+ /**
+ * @param inName
+ * Must be non-null
+ * @return normalized name. All lower-case with no shell meta-characters.
+ */
+ protected static String normalizeName(final String inName) {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < inName.length(); i++) {
+ final char c = inName.charAt(i);
+ sb.append(Character.isLetter(c) || Character.isDigit(c) || c == '-' || c == '_' ? c : '_');
+ }
+ return sb.toString().toUpperCase();
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/MinecraftMapConverter.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/MinecraftMapConverter.java
new file mode 100644
index 0000000..9b91f9f
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/MinecraftMapConverter.java
@@ -0,0 +1,70 @@
+
+package com.ardor3d.example.craft.converter;
+
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.IOException;
+
+import com.ardorcraft.file.WorldFile;
+import com.ardorcraft.world.BlockWorld;
+
+public class MinecraftMapConverter {
+ public static void main(final String[] args) {
+ // Just an example path...
+ new MinecraftMapConverter(new File("/Users/rikardherlitz/Documents/code/MinecraftMap/New World"),
+ new File("worldReal.acr"));
+ }
+
+ private final byte localData[] = new byte[16 * 16 * 128];
+
+ public MinecraftMapConverter(final File minecraftMapRoot, final File outputArdorCraftMap) {
+ try {
+ if (outputArdorCraftMap.exists()) {
+ outputArdorCraftMap.delete();
+ }
+ final WorldFile file = new WorldFile(outputArdorCraftMap);
+
+ for (int x = 0; x < 32; x++) {
+ for (int z = 0; z < 32; z++) {
+ final int chunkX = x - 16;
+ final int chunkZ = z - 16;
+
+ final byte[] data = readChunk(minecraftMapRoot, chunkX, chunkZ);
+ if (data == null) {
+ continue;
+ }
+
+ for (short i = 0; i < 16; i++) {
+ for (short j = 0; j < 16; j++) {
+ for (short k = 0; k < 128; k++) {
+ final byte block = data[i << 11 | j << 7 | k];
+ if (block == 0) {
+ localData[i + (k + j * 128) * 16] = 0; // Air
+ } else if (block == 9) {
+ localData[i + (k + j * 128) * 16] = (byte) BlockWorld.WATER; // Stationary water
+ } else {
+ localData[i + (k + j * 128) * 16] = block; // Rest of the blocks
+ }
+ }
+ }
+ }
+
+ file.save(chunkX, chunkZ, localData);
+ }
+ }
+
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private byte[] readChunk(final File basePath, final int chunkX, final int chunkZ) throws IOException {
+ final DataInputStream istream = RegionFileCache.getChunkDataInputStream(basePath, chunkX, chunkZ);
+ if (istream == null) {
+ return null;
+ }
+ final Tag test = Tag.readFrom(istream);
+ final Tag blocks = test.findTagByName("Blocks");
+ return (byte[]) blocks.getValue();
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/RegionFile.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/RegionFile.java
new file mode 100644
index 0000000..5723717
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/RegionFile.java
@@ -0,0 +1,391 @@
+
+package com.ardor3d.example.craft.converter;
+
+/*
+ ** 2011 January 5
+ **
+ ** The author disclaims copyright to this source code. In place of
+ ** a legal notice, here is a blessing:
+ **
+ ** May you do good and not evil.
+ ** May you find forgiveness for yourself and forgive others.
+ ** May you share freely, never taking more than you give.
+ **/
+
+/*
+ * 2011 February 16
+ *
+ * This source code is based on the work of Scaevolus (see notice above).
+ * It has been slightly modified by Mojang AB (constants instead of magic
+ * numbers, a chunk timestamp header, and auto-formatted according to our
+ * formatter template).
+ *
+ */
+
+// Interfaces with region files on the disk
+
+/*
+
+ Region File Format
+
+ Concept: The minimum unit of storage on hard drives is 4KB. 90% of Minecraft
+ chunks are smaller than 4KB. 99% are smaller than 8KB. Write a simple
+ container to store chunks in single files in runs of 4KB sectors.
+
+ Each region file represents a 32x32 group of chunks. The conversion from
+ chunk number to region number is floor(coord / 32): a chunk at (30, -3)
+ would be in region (0, -1), and one at (70, -30) would be at (3, -1).
+ Region files are named "r.x.z.data", where x and z are the region coordinates.
+
+ A region file begins with a 4KB header that describes where chunks are stored
+ in the file. A 4-byte big-endian integer represents sector offsets and sector
+ counts. The chunk offset for a chunk (x, z) begins at byte 4*(x+z*32) in the
+ file. The bottom byte of the chunk offset indicates the number of sectors the
+ chunk takes up, and the top 3 bytes represent the sector number of the chunk.
+ Given a chunk offset o, the chunk data begins at byte 4096*(o/256) and takes up
+ at most 4096*(o%256) bytes. A chunk cannot exceed 1MB in size. If a chunk
+ offset is 0, the corresponding chunk is not stored in the region file.
+
+ Chunk data begins with a 4-byte big-endian integer representing the chunk data
+ length in bytes, not counting the length field. The length must be smaller than
+ 4096 times the number of sectors. The next byte is a version field, to allow
+ backwards-compatible updates to how chunks are encoded.
+
+ A version of 1 represents a gzipped NBT file. The gzipped data is the chunk
+ length - 1.
+
+ A version of 2 represents a deflated (zlib compressed) NBT file. The deflated
+ data is the chunk length - 1.
+
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.InflaterInputStream;
+
+public class RegionFile {
+
+ private static final int VERSION_GZIP = 1;
+ private static final int VERSION_DEFLATE = 2;
+
+ private static final int SECTOR_BYTES = 4096;
+ private static final int SECTOR_INTS = SECTOR_BYTES / 4;
+
+ static final int CHUNK_HEADER_SIZE = 5;
+ private static final byte emptySector[] = new byte[4096];
+
+ private final File fileName;
+ private RandomAccessFile file;
+ private final int offsets[];
+ private final int chunkTimestamps[];
+ private ArrayList<Boolean> sectorFree;
+ private int sizeDelta;
+ private long lastModified = 0;
+
+ public RegionFile(final File path) {
+ offsets = new int[SECTOR_INTS];
+ chunkTimestamps = new int[SECTOR_INTS];
+
+ fileName = path;
+ debugln("REGION LOAD " + fileName);
+
+ sizeDelta = 0;
+
+ try {
+ if (path.exists()) {
+ lastModified = path.lastModified();
+ }
+
+ file = new RandomAccessFile(path, "rw");
+
+ if (file.length() < SECTOR_BYTES) {
+ /* we need to write the chunk offset table */
+ for (int i = 0; i < SECTOR_INTS; ++i) {
+ file.writeInt(0);
+ }
+ // write another sector for the timestamp info
+ for (int i = 0; i < SECTOR_INTS; ++i) {
+ file.writeInt(0);
+ }
+
+ sizeDelta += SECTOR_BYTES * 2;
+ }
+
+ if ((file.length() & 0xfff) != 0) {
+ /* the file size is not a multiple of 4KB, grow it */
+ for (int i = 0; i < (file.length() & 0xfff); ++i) {
+ file.write((byte) 0);
+ }
+ }
+
+ /* set up the available sector map */
+ final int nSectors = (int) file.length() / SECTOR_BYTES;
+ sectorFree = new ArrayList<Boolean>(nSectors);
+
+ for (int i = 0; i < nSectors; ++i) {
+ sectorFree.add(true);
+ }
+
+ sectorFree.set(0, false); // chunk offset table
+ sectorFree.set(1, false); // for the last modified info
+
+ file.seek(0);
+ for (int i = 0; i < SECTOR_INTS; ++i) {
+ final int offset = file.readInt();
+ offsets[i] = offset;
+ if (offset != 0 && (offset >> 8) + (offset & 0xFF) <= sectorFree.size()) {
+ for (int sectorNum = 0; sectorNum < (offset & 0xFF); ++sectorNum) {
+ sectorFree.set((offset >> 8) + sectorNum, false);
+ }
+ }
+ }
+ for (int i = 0; i < SECTOR_INTS; ++i) {
+ final int lastModValue = file.readInt();
+ chunkTimestamps[i] = lastModValue;
+ }
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /* the modification date of the region file when it was first opened */
+ public long lastModified() {
+ return lastModified;
+ }
+
+ /* gets how much the region file has grown since it was last checked */
+ public synchronized int getSizeDelta() {
+ final int ret = sizeDelta;
+ sizeDelta = 0;
+ return ret;
+ }
+
+ // various small debug printing helpers
+ private void debug(final String in) {
+ // System.out.print(in);
+ }
+
+ private void debugln(final String in) {
+ debug(in + "\n");
+ }
+
+ private void debug(final String mode, final int x, final int z, final String in) {
+ debug("REGION " + mode + " " + fileName.getName() + "[" + x + "," + z + "] = " + in);
+ }
+
+ private void debug(final String mode, final int x, final int z, final int count, final String in) {
+ debug("REGION " + mode + " " + fileName.getName() + "[" + x + "," + z + "] " + count + "B = " + in);
+ }
+
+ private void debugln(final String mode, final int x, final int z, final String in) {
+ debug(mode, x, z, in + "\n");
+ }
+
+ /*
+ * gets an (uncompressed) stream representing the chunk data returns null if the chunk is not found or an error
+ * occurs
+ */
+ public synchronized DataInputStream getChunkDataInputStream(final int x, final int z) {
+ if (outOfBounds(x, z)) {
+ debugln("READ", x, z, "out of bounds");
+ return null;
+ }
+
+ try {
+ final int offset = getOffset(x, z);
+ if (offset == 0) {
+ // debugln("READ", x, z, "miss");
+ return null;
+ }
+
+ final int sectorNumber = offset >> 8;
+ final int numSectors = offset & 0xFF;
+
+ if (sectorNumber + numSectors > sectorFree.size()) {
+ debugln("READ", x, z, "invalid sector");
+ return null;
+ }
+
+ file.seek(sectorNumber * SECTOR_BYTES);
+ final int length = file.readInt();
+
+ if (length > SECTOR_BYTES * numSectors) {
+ debugln("READ", x, z, "invalid length: " + length + " > 4096 * " + numSectors);
+ return null;
+ }
+
+ final byte version = file.readByte();
+ if (version == VERSION_GZIP) {
+ final byte[] data = new byte[length - 1];
+ file.read(data);
+ final DataInputStream ret = new DataInputStream(new GZIPInputStream(new ByteArrayInputStream(data)));
+ // debug("READ", x, z, " = found");
+ return ret;
+ } else if (version == VERSION_DEFLATE) {
+ final byte[] data = new byte[length - 1];
+ file.read(data);
+ final DataInputStream ret = new DataInputStream(
+ new InflaterInputStream(new ByteArrayInputStream(data)));
+ // debug("READ", x, z, " = found");
+ return ret;
+ }
+
+ debugln("READ", x, z, "unknown version " + version);
+ return null;
+ } catch (final IOException e) {
+ debugln("READ", x, z, "exception");
+ return null;
+ }
+ }
+
+ public DataOutputStream getChunkDataOutputStream(final int x, final int z) {
+ if (outOfBounds(x, z)) {
+ return null;
+ }
+
+ return new DataOutputStream(new DeflaterOutputStream(new ChunkBuffer(x, z)));
+ }
+
+ /*
+ * lets chunk writing be multithreaded by not locking the whole file as a chunk is serializing -- only writes when
+ * serialization is over
+ */
+ class ChunkBuffer extends ByteArrayOutputStream {
+ private final int x, z;
+
+ public ChunkBuffer(final int x, final int z) {
+ super(8096); // initialize to 8KB
+ this.x = x;
+ this.z = z;
+ }
+
+ @Override
+ public void close() {
+ RegionFile.this.write(x, z, buf, count);
+ }
+ }
+
+ /* write a chunk at (x,z) with length bytes of data to disk */
+ protected synchronized void write(final int x, final int z, final byte[] data, final int length) {
+ try {
+ final int offset = getOffset(x, z);
+ int sectorNumber = offset >> 8;
+ final int sectorsAllocated = offset & 0xFF;
+ final int sectorsNeeded = (length + CHUNK_HEADER_SIZE) / SECTOR_BYTES + 1;
+
+ // maximum chunk size is 1MB
+ if (sectorsNeeded >= 256) {
+ return;
+ }
+
+ if (sectorNumber != 0 && sectorsAllocated == sectorsNeeded) {
+ /* we can simply overwrite the old sectors */
+ debug("SAVE", x, z, length, "rewrite");
+ write(sectorNumber, data, length);
+ } else {
+ /* we need to allocate new sectors */
+
+ /* mark the sectors previously used for this chunk as free */
+ for (int i = 0; i < sectorsAllocated; ++i) {
+ sectorFree.set(sectorNumber + i, true);
+ }
+
+ /* scan for a free space large enough to store this chunk */
+ int runStart = sectorFree.indexOf(true);
+ int runLength = 0;
+ if (runStart != -1) {
+ for (int i = runStart; i < sectorFree.size(); ++i) {
+ if (runLength != 0) {
+ if (sectorFree.get(i)) {
+ runLength++;
+ } else {
+ runLength = 0;
+ }
+ } else if (sectorFree.get(i)) {
+ runStart = i;
+ runLength = 1;
+ }
+ if (runLength >= sectorsNeeded) {
+ break;
+ }
+ }
+ }
+
+ if (runLength >= sectorsNeeded) {
+ /* we found a free space large enough */
+ debug("SAVE", x, z, length, "reuse");
+ sectorNumber = runStart;
+ setOffset(x, z, sectorNumber << 8 | sectorsNeeded);
+ for (int i = 0; i < sectorsNeeded; ++i) {
+ sectorFree.set(sectorNumber + i, false);
+ }
+ write(sectorNumber, data, length);
+ } else {
+ /*
+ * no free space large enough found -- we need to grow the file
+ */
+ debug("SAVE", x, z, length, "grow");
+ file.seek(file.length());
+ sectorNumber = sectorFree.size();
+ for (int i = 0; i < sectorsNeeded; ++i) {
+ file.write(emptySector);
+ sectorFree.add(false);
+ }
+ sizeDelta += SECTOR_BYTES * sectorsNeeded;
+
+ write(sectorNumber, data, length);
+ setOffset(x, z, sectorNumber << 8 | sectorsNeeded);
+ }
+ }
+ setTimestamp(x, z, (int) (System.currentTimeMillis() / 1000L));
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /* write a chunk data to the region file at specified sector number */
+ private void write(final int sectorNumber, final byte[] data, final int length) throws IOException {
+ debugln(" " + sectorNumber);
+ file.seek(sectorNumber * SECTOR_BYTES);
+ file.writeInt(length + 1); // chunk length
+ file.writeByte(VERSION_DEFLATE); // chunk version number
+ file.write(data, 0, length); // chunk data
+ }
+
+ /* is this an invalid chunk coordinate? */
+ private boolean outOfBounds(final int x, final int z) {
+ return x < 0 || x >= 32 || z < 0 || z >= 32;
+ }
+
+ private int getOffset(final int x, final int z) {
+ return offsets[x + z * 32];
+ }
+
+ public boolean hasChunk(final int x, final int z) {
+ return getOffset(x, z) != 0;
+ }
+
+ private void setOffset(final int x, final int z, final int offset) throws IOException {
+ offsets[x + z * 32] = offset;
+ file.seek((x + z * 32) * 4);
+ file.writeInt(offset);
+ }
+
+ private void setTimestamp(final int x, final int z, final int value) throws IOException {
+ chunkTimestamps[x + z * 32] = value;
+ file.seek(SECTOR_BYTES + (x + z * 32) * 4);
+ file.writeInt(value);
+ }
+
+ public void close() throws IOException {
+ file.close();
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/RegionFileCache.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/RegionFileCache.java
new file mode 100644
index 0000000..87d86a0
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/RegionFileCache.java
@@ -0,0 +1,97 @@
+
+package com.ardor3d.example.craft.converter;
+
+/*
+ ** 2011 January 5
+ **
+ ** The author disclaims copyright to this source code. In place of
+ ** a legal notice, here is a blessing:
+ **
+ ** May you do good and not evil.
+ ** May you find forgiveness for yourself and forgive others.
+ ** May you share freely, never taking more than you give.
+ */
+
+/*
+ * 2011 February 16
+ *
+ * This source code is based on the work of Scaevolus (see notice above).
+ * It has been slightly modified by Mojang AB to limit the maximum cache
+ * size (relevant to extremely big worlds on Linux systems with limited
+ * number of file handles). The region files are postfixed with ".mcr"
+ * (Minecraft region file) instead of ".data" to differentiate from the
+ * original McRegion files.
+ *
+ */
+
+// A simple cache and wrapper for efficiently multiple RegionFiles simultaneously.
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+import java.util.Map;
+
+public class RegionFileCache {
+
+ private static final int MAX_CACHE_SIZE = 256;
+
+ private static final Map<File, Reference<RegionFile>> cache = new HashMap<File, Reference<RegionFile>>();
+
+ private RegionFileCache() {
+ }
+
+ public static synchronized RegionFile getRegionFile(final File basePath, final int chunkX, final int chunkZ) {
+ final File regionDir = new File(basePath, "region");
+ final File file = new File(regionDir, "r." + (chunkX >> 5) + "." + (chunkZ >> 5) + ".mcr");
+
+ final Reference<RegionFile> ref = cache.get(file);
+
+ if (ref != null && ref.get() != null) {
+ return ref.get();
+ }
+
+ if (!regionDir.exists()) {
+ regionDir.mkdirs();
+ }
+
+ if (cache.size() >= MAX_CACHE_SIZE) {
+ RegionFileCache.clear();
+ }
+
+ final RegionFile reg = new RegionFile(file);
+ cache.put(file, new SoftReference<RegionFile>(reg));
+ return reg;
+ }
+
+ public static synchronized void clear() {
+ for (final Reference<RegionFile> ref : cache.values()) {
+ try {
+ if (ref.get() != null) {
+ ref.get().close();
+ }
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+ cache.clear();
+ }
+
+ public static int getSizeDelta(final File basePath, final int chunkX, final int chunkZ) {
+ final RegionFile r = getRegionFile(basePath, chunkX, chunkZ);
+ return r.getSizeDelta();
+ }
+
+ public static DataInputStream getChunkDataInputStream(final File basePath, final int chunkX, final int chunkZ) {
+ final RegionFile r = getRegionFile(basePath, chunkX, chunkZ);
+ return r.getChunkDataInputStream(chunkX & 31, chunkZ & 31);
+ }
+
+ public static DataOutputStream getChunkDataOutputStream(final File basePath, final int chunkX, final int chunkZ) {
+ final RegionFile r = getRegionFile(basePath, chunkX, chunkZ);
+ return r.getChunkDataOutputStream(chunkX & 31, chunkZ & 31);
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/Tag.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/Tag.java
new file mode 100644
index 0000000..5bf40c6
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/converter/Tag.java
@@ -0,0 +1,508 @@
+
+package com.ardor3d.example.craft.converter;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * NBT IO class
+ *
+ * @see <a href="http://www.minecraft.net/docs/NBT.txt">Online NBT specification</a>
+ */
+public class Tag {
+ private final Type type;
+ private Type listType = null;
+ private final String name;
+ private Object value;
+
+ /**
+ * Enum for the tag types.
+ */
+ public enum Type {
+ TAG_End, TAG_Byte, TAG_Short, TAG_Int, TAG_Long, TAG_Float, TAG_Double, TAG_Byte_Array, TAG_String, TAG_List, TAG_Compound
+ }
+
+ /**
+ * Create a new TAG_List or TAG_Compound NBT tag.
+ *
+ * @param type
+ * either TAG_List or TAG_Compound
+ * @param name
+ * name for the new tag or null to create an unnamed tag.
+ * @param value
+ * list of tags to add to the new tag.
+ */
+ public Tag(final Type type, final String name, final Tag[] value) {
+ this(type, name, (Object) value);
+ }
+
+ /**
+ * Create a new TAG_List with an empty list. Use {@link Tag#addTag(Tag)} to add tags later.
+ *
+ * @param name
+ * name for this tag or null to create an unnamed tag.
+ * @param listType
+ * type of the elements in this empty list.
+ */
+ public Tag(final String name, final Type listType) {
+ this(Type.TAG_List, name, listType);
+ }
+
+ /**
+ * Create a new NBT tag.
+ *
+ * @param type
+ * any value from the {@link Type} enum.
+ * @param name
+ * name for the new tag or null to create an unnamed tag.
+ * @param value
+ * an object that fits the tag type or a {@link Type} to create an empty TAG_List with this list type.
+ */
+ public Tag(final Type type, final String name, Object value) {
+ if (type == Type.TAG_Compound) {
+ if (!(value instanceof Tag[])) {
+ throw new IllegalArgumentException();
+ }
+ }
+ switch (type) {
+ case TAG_End:
+ if (value != null) {
+ throw new IllegalArgumentException();
+ }
+ break;
+ case TAG_Byte:
+ if (!(value instanceof Byte)) {
+ throw new IllegalArgumentException();
+ }
+ break;
+ case TAG_Short:
+ if (!(value instanceof Short)) {
+ throw new IllegalArgumentException();
+ }
+ break;
+ case TAG_Int:
+ if (!(value instanceof Integer)) {
+ throw new IllegalArgumentException();
+ }
+ break;
+ case TAG_Long:
+ if (!(value instanceof Long)) {
+ throw new IllegalArgumentException();
+ }
+ break;
+ case TAG_Float:
+ if (!(value instanceof Float)) {
+ throw new IllegalArgumentException();
+ }
+ break;
+ case TAG_Double:
+ if (!(value instanceof Double)) {
+ throw new IllegalArgumentException();
+ }
+ break;
+ case TAG_Byte_Array:
+ if (!(value instanceof byte[])) {
+ throw new IllegalArgumentException();
+ }
+ break;
+ case TAG_String:
+ if (!(value instanceof String)) {
+ throw new IllegalArgumentException();
+ }
+ break;
+ case TAG_List:
+ if (value instanceof Type) {
+ listType = (Type) value;
+ value = new Tag[0];
+ } else {
+ if (!(value instanceof Tag[])) {
+ throw new IllegalArgumentException();
+ }
+ listType = ((Tag[]) value)[0].getType();
+ }
+ break;
+ case TAG_Compound:
+ if (!(value instanceof Tag[])) {
+ throw new IllegalArgumentException();
+ }
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ this.type = type;
+ this.name = name;
+ this.value = value;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public Type getListType() {
+ return listType;
+ }
+
+ /**
+ * Add a tag to a TAG_List or a TAG_Compound.
+ */
+ public void addTag(final Tag tag) {
+ if (type != Type.TAG_List && type != Type.TAG_Compound) {
+ throw new RuntimeException();
+ }
+ final Tag[] subtags = (Tag[]) value;
+ insertTag(tag, subtags.length);
+ }
+
+ /**
+ * Add a tag to a TAG_List or a TAG_Compound at the specified index.
+ */
+ public void insertTag(final Tag tag, final int index) {
+ if (type != Type.TAG_List && type != Type.TAG_Compound) {
+ throw new RuntimeException();
+ }
+ final Tag[] subtags = (Tag[]) value;
+ if (subtags.length > 0) {
+ if (type == Type.TAG_List && tag.getType() != getListType()) {
+ throw new IllegalArgumentException();
+ }
+ }
+ if (index > subtags.length) {
+ throw new IndexOutOfBoundsException();
+ }
+ final Tag[] newValue = new Tag[subtags.length + 1];
+ System.arraycopy(subtags, 0, newValue, 0, index);
+ newValue[index] = tag;
+ System.arraycopy(subtags, index, newValue, index + 1, subtags.length - index);
+ value = newValue;
+ }
+
+ /**
+ * Remove a tag from a TAG_List or a TAG_Compound at the specified index.
+ *
+ * @return the removed tag
+ */
+ public Tag removeTag(int index) {
+ if (type != Type.TAG_List && type != Type.TAG_Compound) {
+ throw new RuntimeException();
+ }
+ final Tag[] subtags = (Tag[]) value;
+ final Tag victim = subtags[index];
+ final Tag[] newValue = new Tag[subtags.length - 1];
+ System.arraycopy(subtags, 0, newValue, 0, index);
+ index++;
+ System.arraycopy(subtags, index, newValue, index - 1, subtags.length - index);
+ value = newValue;
+ return victim;
+ }
+
+ /**
+ * Remove a tag from a TAG_List or a TAG_Compound. If the tag is not a child of this tag then nested tags are
+ * searched.
+ *
+ * @param tag
+ * tag to look for
+ */
+ public void removeSubTag(final Tag tag) {
+ if (type != Type.TAG_List && type != Type.TAG_Compound) {
+ throw new RuntimeException();
+ }
+ if (tag == null) {
+ return;
+ }
+ final Tag[] subtags = (Tag[]) value;
+ for (int i = 0; i < subtags.length; i++) {
+ if (subtags[i] == tag) {
+ removeTag(i);
+ return;
+ } else {
+ if (subtags[i].type == Type.TAG_List || subtags[i].type == Type.TAG_Compound) {
+ subtags[i].removeSubTag(tag);
+ }
+ }
+ }
+ }
+
+ /**
+ * Find the first nested tag with specified name in a TAG_Compound.
+ *
+ * @param name
+ * the name to look for. May be null to look for unnamed tags.
+ * @return the first nested tag that has the specified name.
+ */
+ public Tag findTagByName(final String name) {
+ return findNextTagByName(name, null);
+ }
+
+ /**
+ * Find the first nested tag with specified name in a TAG_List or TAG_Compound after a tag with the same name.
+ *
+ * @param name
+ * the name to look for. May be null to look for unnamed tags.
+ * @param found
+ * the previously found tag with the same name.
+ * @return the first nested tag that has the specified name after the previously found tag.
+ */
+ public Tag findNextTagByName(final String name, final Tag found) {
+ if (type != Type.TAG_List && type != Type.TAG_Compound) {
+ return null;
+ }
+ final Tag[] subtags = (Tag[]) value;
+ for (final Tag subtag : subtags) {
+ if (subtag.name == null && name == null || subtag.name != null && subtag.name.equals(name)) {
+ return subtag;
+ } else {
+ final Tag newFound = subtag.findTagByName(name);
+ if (newFound != null) {
+ if (newFound == found) {
+ continue;
+ } else {
+ return newFound;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Read a tag and its nested tags from an InputStream.
+ *
+ * @param is
+ * stream to read from, like a FileInputStream
+ * @return NBT tag or structure read from the InputStream
+ * @throws IOException
+ * if there was no valid NBT structure in the InputStream or if another IOException occurred.
+ */
+ public static Tag readFrom(final DataInputStream dis) throws IOException {
+ // final DataInputStream dis = new DataInputStream(new GZIPInputStream(is));
+ final byte type = dis.readByte();
+ Tag tag = null;
+
+ if (type == 0) {
+ tag = new Tag(Type.TAG_End, null, null);
+ } else {
+ tag = new Tag(Type.values()[type], dis.readUTF(), readPayload(dis, type));
+ }
+
+ dis.close();
+
+ return tag;
+ }
+
+ private static Object readPayload(final DataInputStream dis, final byte type) throws IOException {
+ switch (type) {
+ case 0:
+ return null;
+ case 1:
+ return dis.readByte();
+ case 2:
+ return dis.readShort();
+ case 3:
+ return dis.readInt();
+ case 4:
+ return dis.readLong();
+ case 5:
+ return dis.readFloat();
+ case 6:
+ return dis.readDouble();
+ case 7:
+ final int length = dis.readInt();
+ final byte[] ba = new byte[length];
+ dis.readFully(ba);
+ return ba;
+ case 8:
+ return dis.readUTF();
+ case 9:
+ final byte lt = dis.readByte();
+ final int ll = dis.readInt();
+ final Tag[] lo = new Tag[ll];
+ for (int i = 0; i < ll; i++) {
+ lo[i] = new Tag(Type.values()[lt], null, readPayload(dis, lt));
+ }
+ if (lo.length == 0) {
+ return Type.values()[lt];
+ } else {
+ return lo;
+ }
+ case 10:
+ byte stt;
+ Tag[] tags = new Tag[0];
+ do {
+ stt = dis.readByte();
+ String name = null;
+ if (stt != 0) {
+ name = dis.readUTF();
+ }
+ final Tag[] newTags = new Tag[tags.length + 1];
+ System.arraycopy(tags, 0, newTags, 0, tags.length);
+ newTags[tags.length] = new Tag(Type.values()[stt], name, readPayload(dis, stt));
+ tags = newTags;
+ } while (stt != 0);
+ return tags;
+ }
+ return null;
+ }
+
+ /**
+ * Read a tag and its nested tags from an InputStream.
+ *
+ * @param os
+ * stream to write to, like a FileOutputStream
+ * @throws IOException
+ * if this is not a valid NBT structure or if any IOException occurred.
+ */
+ public void writeTo(final OutputStream os) throws IOException {
+ GZIPOutputStream gzos;
+ final DataOutputStream dos = new DataOutputStream(gzos = new GZIPOutputStream(os));
+ dos.writeByte(type.ordinal());
+ if (type != Type.TAG_End) {
+ dos.writeUTF(name);
+ writePayload(dos);
+ }
+ gzos.flush();
+ gzos.close();
+ }
+
+ private void writePayload(final DataOutputStream dos) throws IOException {
+ switch (type) {
+ case TAG_End:
+ break;
+ case TAG_Byte:
+ dos.writeByte((Byte) value);
+ break;
+ case TAG_Short:
+ dos.writeShort((Short) value);
+ break;
+ case TAG_Int:
+ dos.writeInt((Integer) value);
+ break;
+ case TAG_Long:
+ dos.writeLong((Long) value);
+ break;
+ case TAG_Float:
+ dos.writeFloat((Float) value);
+ break;
+ case TAG_Double:
+ dos.writeDouble((Double) value);
+ break;
+ case TAG_Byte_Array:
+ final byte[] ba = (byte[]) value;
+ dos.writeInt(ba.length);
+ dos.write(ba);
+ break;
+ case TAG_String:
+ dos.writeUTF((String) value);
+ break;
+ case TAG_List:
+ final Tag[] list = (Tag[]) value;
+ dos.writeByte(getListType().ordinal());
+ dos.writeInt(list.length);
+ for (final Tag tt : list) {
+ tt.writePayload(dos);
+ }
+ break;
+ case TAG_Compound:
+ final Tag[] subtags = (Tag[]) value;
+ for (final Tag st : subtags) {
+ final Tag subtag = st;
+ final Type type = subtag.getType();
+ dos.writeByte(type.ordinal());
+ if (type != Type.TAG_End) {
+ dos.writeUTF(subtag.getName());
+ subtag.writePayload(dos);
+ }
+ }
+ break;
+ }
+ }
+
+ /**
+ * Print the NBT structure to System.out
+ */
+ public void print() {
+ print(this, 0);
+ }
+
+ private String getTypeString(final Type type) {
+ switch (type) {
+ case TAG_End:
+ return "TAG_End";
+ case TAG_Byte:
+ return "TAG_Byte";
+ case TAG_Short:
+ return "TAG_Short";
+ case TAG_Int:
+ return "TAG_Int";
+ case TAG_Long:
+ return "TAG_Long";
+ case TAG_Float:
+ return "TAG_Float";
+ case TAG_Double:
+ return "TAG_Double";
+ case TAG_Byte_Array:
+ return "TAG_Byte_Array";
+ case TAG_String:
+ return "TAG_String";
+ case TAG_List:
+ return "TAG_List";
+ case TAG_Compound:
+ return "TAG_Compound";
+ }
+ return null;
+ }
+
+ private void indent(final int indent) {
+ for (int i = 0; i < indent; i++) {
+ System.out.print(" ");
+ }
+ }
+
+ private void print(final Tag t, final int indent) {
+ final Type type = t.getType();
+ if (type == Type.TAG_End) {
+ return;
+ }
+ final String name = t.getName();
+ indent(indent);
+ System.out.print(getTypeString(t.getType()));
+ if (name != null) {
+ System.out.print("(\"" + t.getName() + "\")");
+ }
+ if (type == Type.TAG_Byte_Array) {
+ final byte[] b = (byte[]) t.getValue();
+ System.out.println(": [" + b.length + " bytes]");
+ } else if (type == Type.TAG_List) {
+ final Tag[] subtags = (Tag[]) t.getValue();
+ System.out.println(": " + subtags.length + " entries of type " + getTypeString(t.getListType()));
+ for (final Tag st : subtags) {
+ print(st, indent + 1);
+ }
+ indent(indent);
+ System.out.println("}");
+ } else if (type == Type.TAG_Compound) {
+ final Tag[] subtags = (Tag[]) t.getValue();
+ System.out.println(": " + (subtags.length - 1) + " entries");
+ indent(indent);
+ System.out.println("{");
+ for (final Tag st : subtags) {
+ print(st, indent + 1);
+ }
+ indent(indent);
+ System.out.println("}");
+ } else {
+ System.out.println(": " + t.getValue());
+ }
+ }
+
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/advanced/AdvancedApplication.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/advanced/AdvancedApplication.java
new file mode 100644
index 0000000..562a5ea
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/advanced/AdvancedApplication.java
@@ -0,0 +1,16 @@
+
+package com.ardor3d.example.craft.examples.advanced;
+
+import com.ardor3d.example.craft.base.ArdorBaseApplication;
+
+public class AdvancedApplication extends ArdorBaseApplication {
+
+ public AdvancedApplication() {
+ super(new AdvancedGame());
+ }
+
+ public static void main(final String[] args) {
+ final ArdorBaseApplication example = new AdvancedApplication();
+ new Thread(example, "MainArdorThread").start();
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/advanced/AdvancedGame.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/advanced/AdvancedGame.java
new file mode 100644
index 0000000..14462b3
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/advanced/AdvancedGame.java
@@ -0,0 +1,213 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.example.craft.examples.advanced;
+
+import java.net.URISyntaxException;
+
+import com.ardor3d.example.craft.base.ArdorCraftGame;
+import com.ardor3d.example.craft.base.CanvasRelayer;
+import com.ardor3d.example.craft.generators.NiceDataGenerator;
+import com.ardor3d.example.craft.network.LocalServerConnection;
+import com.ardor3d.example.craft.network.LocalServerDataHandler;
+import com.ardor3d.example.craft.player.PlayerWithPhysics;
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.input.GrabbedState;
+import com.ardor3d.input.Key;
+import com.ardor3d.input.MouseButton;
+import com.ardor3d.input.MouseManager;
+import com.ardor3d.input.PhysicalLayer;
+import com.ardor3d.input.logical.InputTrigger;
+import com.ardor3d.input.logical.KeyHeldCondition;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.input.logical.MouseButtonPressedCondition;
+import com.ardor3d.input.logical.TriggerAction;
+import com.ardor3d.input.logical.TwoInputStates;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.FogState;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.ui.text.BasicText;
+import com.ardor3d.util.ReadOnlyTimer;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.ardor3d.util.resource.SimpleResourceLocator;
+import com.ardorcraft.collision.IntersectionResult;
+import com.ardorcraft.data.Pos;
+import com.ardorcraft.world.BlockWorld;
+import com.ardorcraft.world.IServerConnection;
+import com.ardorcraft.world.WorldSettings;
+
+/**
+ * Adds physics and some other things to the intermediate example
+ */
+public class AdvancedGame implements ArdorCraftGame {
+
+ private BlockWorld blockWorld;
+ private final int tileSize = 16;
+ private final int height = 100;
+ private final int gridSize = 20;
+ private final double farPlane = (gridSize - 1) / 2 * tileSize;
+
+ private final ReadOnlyColorRGBA fogColor = new ColorRGBA(0.9f, 0.9f, 1.0f, 1.0f);
+ private Node root;
+ private Camera camera;
+ private PlayerWithPhysics player;
+
+ @Override
+ public void update(final ReadOnlyTimer timer) {
+ player.update(blockWorld, timer);
+
+ camera.setLocation(player.getPosition());
+ camera.setDirection(player.getDirection());
+ camera.setUp(player.getUp());
+ camera.setLeft(player.getLeft());
+
+ // The infinite world update
+ blockWorld.updatePlayer(player.getPosition(), player.getDirection());
+ blockWorld.update(timer);
+ }
+
+ @Override
+ public void render(final Renderer renderer) {
+ root.draw(renderer);
+ }
+
+ @Override
+ public void init(final Node root, final CanvasRelayer canvas, final LogicalLayer logicalLayer,
+ final PhysicalLayer physicalLayer, final MouseManager mouseManager) {
+ this.root = root;
+
+ try {
+ final SimpleResourceLocator srl = new SimpleResourceLocator(
+ ResourceLocatorTool.getClassPathResource(AdvancedGame.class, "com/ardorcraft/resources"));
+ ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_TEXTURE, srl);
+ ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_MODEL, srl);
+ } catch (final URISyntaxException ex) {
+ ex.printStackTrace();
+ }
+
+ canvas.setTitle("ArdorCraft API Example - AdvancedGame.java");
+ canvas.getCanvasRenderer().getRenderer().setBackgroundColor(fogColor);
+
+ camera = canvas.getCanvasRenderer().getCamera();
+ camera.setFrustumPerspective(75.0, (float) camera.getWidth() / (float) camera.getHeight(), 0.1, farPlane);
+
+ setupFog();
+
+ // Create player object
+ player = new PlayerWithPhysics(logicalLayer);
+ player.setWalking(true);
+ player.getPosition().set(15, 50, 15);
+
+ registerTriggers(logicalLayer, mouseManager);
+
+ // Create block world. Not setting any map file leads to automatic creation of a world.acr map file (which is
+ // overwritten at each run)
+ final WorldSettings settings = new WorldSettings();
+ settings.setTerrainTexture(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, "terrain.png"));
+ settings.setTerrainTextureTileSize(32);
+ settings.setWaterTexture(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, "water.png"));
+ settings.setTileSize(tileSize);
+ settings.setTileHeight(height);
+ settings.setGridSize(gridSize);
+
+ // Create a local "fake" server
+ final IServerConnection serverConnection = new LocalServerConnection(
+ new LocalServerDataHandler(tileSize, height, gridSize, new NiceDataGenerator(), null));
+ settings.setServerConnection(serverConnection);
+
+ blockWorld = new BlockWorld(settings);
+
+ root.attachChild(blockWorld.getWorldNode());
+
+ final BasicText cross = BasicText.createDefaultTextLabel("Text", "+", 16);
+ cross.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ cross.setTranslation(new Vector3(canvas.getCanvasRenderer().getCamera().getWidth() / 2 - 5,
+ canvas.getCanvasRenderer().getCamera().getHeight() / 2 - 10, 0));
+ root.attachChild(cross);
+
+ blockWorld.startThreads();
+ }
+
+ private void setupFog() {
+ final FogState fogState = new FogState();
+ fogState.setDensity(1.0f);
+ fogState.setEnabled(true);
+ fogState.setColor(fogColor);
+ fogState.setEnd((float) farPlane);
+ fogState.setStart((float) farPlane / 3.0f);
+ fogState.setDensityFunction(FogState.DensityFunction.Linear);
+ fogState.setQuality(FogState.Quality.PerPixel);
+ root.setRenderState(fogState);
+ }
+
+ private final IntersectionResult intersectionResult = new IntersectionResult();
+
+ private void registerTriggers(final LogicalLayer logicalLayer, final MouseManager mouseManager) {
+ logicalLayer.registerTrigger(
+ new InputTrigger(new MouseButtonPressedCondition(MouseButton.LEFT), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ addBlock();
+ }
+ }));
+
+ logicalLayer.registerTrigger(
+ new InputTrigger(new MouseButtonPressedCondition(MouseButton.RIGHT), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
+ removeBlock();
+ }
+ }));
+
+ logicalLayer.registerTrigger(new InputTrigger(new KeyHeldCondition(Key.SPACE), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ player.jump();
+ }
+ }));
+
+ if (mouseManager.isSetGrabbedSupported()) {
+ mouseManager.setGrabbed(GrabbedState.GRABBED);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ blockWorld.stopThreads();
+ }
+
+ @Override
+ public void resize(final int newWidth, final int newHeight) {
+ }
+
+ private void addBlock() {
+ blockWorld.tracePicking(player.getPosition(), player.getDirection(), 200, intersectionResult);
+ if (intersectionResult.hit) {
+ final Pos addPos = intersectionResult.oldPos;
+ if (!player.isPlayerSpace(addPos)) {
+ blockWorld.setBlock(addPos.x, addPos.y, addPos.z, 1);
+ }
+ }
+ }
+
+ private void removeBlock() {
+ blockWorld.tracePicking(player.getPosition(), player.getDirection(), 200, intersectionResult);
+ if (intersectionResult.hit) {
+ final Pos deletePos = intersectionResult.pos;
+ blockWorld.setBlock(deletePos.x, deletePos.y, deletePos.z, 0);
+ }
+ }
+
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/intermediate/IntermediateApplication.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/intermediate/IntermediateApplication.java
new file mode 100644
index 0000000..d3aac26
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/intermediate/IntermediateApplication.java
@@ -0,0 +1,16 @@
+
+package com.ardor3d.example.craft.examples.intermediate;
+
+import com.ardor3d.example.craft.base.ArdorBaseApplication;
+
+public class IntermediateApplication extends ArdorBaseApplication {
+
+ public IntermediateApplication() {
+ super(new IntermediateGame());
+ }
+
+ public static void main(final String[] args) {
+ final ArdorBaseApplication example = new IntermediateApplication();
+ new Thread(example, "MainArdorThread").start();
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/intermediate/IntermediateGame.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/intermediate/IntermediateGame.java
new file mode 100644
index 0000000..bc2e46a
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/intermediate/IntermediateGame.java
@@ -0,0 +1,238 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.example.craft.examples.intermediate;
+
+import java.net.URISyntaxException;
+
+import com.ardor3d.example.craft.base.ArdorCraftGame;
+import com.ardor3d.example.craft.base.CanvasRelayer;
+import com.ardor3d.example.craft.generators.InterpolatedNoiseDataGenerator;
+import com.ardor3d.example.craft.network.LocalServerConnection;
+import com.ardor3d.example.craft.network.LocalServerDataHandler;
+import com.ardor3d.example.craft.player.PlayerWithCollision;
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.input.GrabbedState;
+import com.ardor3d.input.Key;
+import com.ardor3d.input.MouseButton;
+import com.ardor3d.input.MouseManager;
+import com.ardor3d.input.PhysicalLayer;
+import com.ardor3d.input.logical.InputTrigger;
+import com.ardor3d.input.logical.KeyPressedCondition;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.input.logical.MouseButtonPressedCondition;
+import com.ardor3d.input.logical.TriggerAction;
+import com.ardor3d.input.logical.TwoInputStates;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.FogState;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.ui.text.BasicText;
+import com.ardor3d.util.ReadOnlyTimer;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.ardor3d.util.resource.SimpleResourceLocator;
+import com.ardorcraft.collision.IntersectionResult;
+import com.ardorcraft.control.FlyControl;
+import com.ardorcraft.data.Pos;
+import com.ardorcraft.world.BlockWorld;
+import com.ardorcraft.world.IServerConnection;
+import com.ardorcraft.world.WorldSettings;
+
+/**
+ * Adds some collision and block add/delete functionality to the simple example
+ */
+public class IntermediateGame implements ArdorCraftGame {
+
+ private BlockWorld blockWorld;
+ private final int tileSize = 16;
+ private final int gridSize = 16;
+ private final int height = 100;
+ private final double farPlane = (gridSize - 1) / 2 * tileSize;
+
+ private final ReadOnlyColorRGBA fogColor = new ColorRGBA(0.9f, 0.9f, 1.0f, 1.0f);
+ private Node root;
+ private Camera camera;
+ private PlayerWithCollision player;
+
+ @Override
+ public void update(final ReadOnlyTimer timer) {
+ player.update(blockWorld, timer);
+
+ camera.setLocation(player.getPosition());
+ camera.setDirection(player.getDirection());
+ camera.setUp(player.getUp());
+ camera.setLeft(player.getLeft());
+
+ // The infinite world update
+ blockWorld.updatePlayer(player.getPosition(), player.getDirection());
+ blockWorld.update(timer);
+ }
+
+ @Override
+ public void render(final Renderer renderer) {
+ root.draw(renderer);
+ }
+
+ @Override
+ public void init(final Node root, final CanvasRelayer canvas, final LogicalLayer logicalLayer,
+ final PhysicalLayer physicalLayer, final MouseManager mouseManager) {
+ this.root = root;
+
+ try {
+ final SimpleResourceLocator srl = new SimpleResourceLocator(
+ ResourceLocatorTool.getClassPathResource(IntermediateGame.class, "com/ardorcraft/resources"));
+ ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_TEXTURE, srl);
+ ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_MODEL, srl);
+ } catch (final URISyntaxException ex) {
+ ex.printStackTrace();
+ }
+
+ canvas.setTitle("ArdorCraft API Example - IntermediateGame.java");
+ canvas.getCanvasRenderer().getRenderer().setBackgroundColor(fogColor);
+
+ camera = canvas.getCanvasRenderer().getCamera();
+ camera.setFrustumPerspective(75.0, (float) camera.getWidth() / (float) camera.getHeight(), 0.1, farPlane);
+
+ setupFog();
+
+ // Create player object
+ player = new PlayerWithCollision();
+ player.getPosition().set(0, 50, 0);
+ FlyControl.setupTriggers(player, logicalLayer, Vector3.UNIT_Y, false);
+
+ registerTriggers(logicalLayer, mouseManager);
+
+ // Create block world settings.
+ final WorldSettings settings = new WorldSettings();
+ settings.setTerrainTexture(
+ ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, "terrainQ.png"));
+ settings.setTerrainTextureTileSize(16);
+ settings.setWaterTexture(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, "water.png"));
+ settings.setTileSize(tileSize);
+ settings.setTileHeight(height);
+ settings.setGridSize(gridSize);
+
+ // Create a local "fake" server
+ final IServerConnection serverConnection = new LocalServerConnection(
+ new LocalServerDataHandler(tileSize, height, gridSize, new InterpolatedNoiseDataGenerator(), null));
+ settings.setServerConnection(serverConnection);
+
+ blockWorld = new BlockWorld(settings);
+
+ root.attachChild(blockWorld.getWorldNode());
+
+ final BasicText cross = BasicText.createDefaultTextLabel("Text", "+", 16);
+ cross.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ cross.setTranslation(new Vector3(canvas.getCanvasRenderer().getCamera().getWidth() / 2 - 5,
+ canvas.getCanvasRenderer().getCamera().getHeight() / 2 - 10, 0));
+ root.attachChild(cross);
+
+ blockWorld.startThreads();
+ }
+
+ private void setupFog() {
+ final FogState fogState = new FogState();
+ fogState.setDensity(1.0f);
+ fogState.setEnabled(true);
+ fogState.setColor(fogColor);
+ fogState.setEnd((float) farPlane);
+ fogState.setStart((float) farPlane / 3.0f);
+ fogState.setDensityFunction(FogState.DensityFunction.Linear);
+ fogState.setQuality(FogState.Quality.PerPixel);
+ root.setRenderState(fogState);
+ }
+
+ private final IntersectionResult intersectionResult = new IntersectionResult();
+
+ private void registerTriggers(final LogicalLayer logicalLayer, final MouseManager mouseManager) {
+ logicalLayer.registerTrigger(
+ new InputTrigger(new MouseButtonPressedCondition(MouseButton.LEFT), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ addBlock();
+ }
+ }));
+
+ logicalLayer.registerTrigger(
+ new InputTrigger(new MouseButtonPressedCondition(MouseButton.RIGHT), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
+ removeBlock();
+ }
+ }));
+
+ logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ blockWorld.tracePicking(player.getPosition(), player.getDirection(), 200, intersectionResult);
+ if (intersectionResult.hit) {
+ final Pos addPos = intersectionResult.oldPos;
+ for (int x = 0; x < 3; x++) {
+ for (int y = 0; y < 3; y++) {
+ for (int z = 0; z < 3; z++) {
+ blockWorld.setBlock(addPos.x + x - 1, addPos.y + y - 1, addPos.z + z - 1, 3);
+ }
+ }
+ }
+ }
+ }
+ }));
+ logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.G), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ blockWorld.tracePicking(player.getPosition(), player.getDirection(), 200, intersectionResult);
+ if (intersectionResult.hit) {
+ final Pos addPos = intersectionResult.pos;
+ for (int x = 0; x < 3; x++) {
+ for (int y = 0; y < 3; y++) {
+ for (int z = 0; z < 3; z++) {
+ blockWorld.setBlock(addPos.x + x - 1, addPos.y + y - 1, addPos.z + z - 1, 0);
+ }
+ }
+ }
+ }
+ }
+ }));
+
+ if (mouseManager.isSetGrabbedSupported()) {
+ mouseManager.setGrabbed(GrabbedState.GRABBED);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ blockWorld.stopThreads();
+ }
+
+ @Override
+ public void resize(final int newWidth, final int newHeight) {
+ }
+
+ private void addBlock() {
+ blockWorld.tracePicking(player.getPosition(), player.getDirection(), 200, intersectionResult);
+ if (intersectionResult.hit) {
+ final Pos addPos = intersectionResult.oldPos;
+ blockWorld.setBlock(addPos.x, addPos.y, addPos.z, 3);
+ }
+ }
+
+ private void removeBlock() {
+ blockWorld.tracePicking(player.getPosition(), player.getDirection(), 200, intersectionResult);
+ if (intersectionResult.hit) {
+ final Pos deletePos = intersectionResult.pos;
+ blockWorld.setBlock(deletePos.x, deletePos.y, deletePos.z, 0);
+ }
+ }
+
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/simple/SimpleApplication.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/simple/SimpleApplication.java
new file mode 100644
index 0000000..aeb9818
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/simple/SimpleApplication.java
@@ -0,0 +1,16 @@
+
+package com.ardor3d.example.craft.examples.simple;
+
+import com.ardor3d.example.craft.base.ArdorBaseApplication;
+
+public class SimpleApplication extends ArdorBaseApplication {
+
+ public SimpleApplication() {
+ super(new SimpleGame());
+ }
+
+ public static void main(final String[] args) {
+ final ArdorBaseApplication example = new SimpleApplication();
+ new Thread(example, "MainArdorThread").start();
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/simple/SimpleGame.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/simple/SimpleGame.java
new file mode 100644
index 0000000..d096195
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/simple/SimpleGame.java
@@ -0,0 +1,149 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.example.craft.examples.simple;
+
+import java.net.URISyntaxException;
+
+import com.ardor3d.example.craft.base.ArdorCraftGame;
+import com.ardor3d.example.craft.base.CanvasRelayer;
+import com.ardor3d.example.craft.generators.LayerDataGenerator;
+import com.ardor3d.example.craft.network.LocalServerConnection;
+import com.ardor3d.example.craft.network.LocalServerDataHandler;
+import com.ardor3d.input.MouseManager;
+import com.ardor3d.input.PhysicalLayer;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.util.ReadOnlyTimer;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.ardor3d.util.resource.SimpleResourceLocator;
+import com.ardorcraft.control.FlyControl;
+import com.ardorcraft.player.PlayerBase;
+import com.ardorcraft.world.BlockWorld;
+import com.ardorcraft.world.IServerConnection;
+import com.ardorcraft.world.WorldModifier;
+import com.ardorcraft.world.WorldSettings;
+
+/**
+ * A simple example showing the very basics of block world building
+ */
+public class SimpleGame implements ArdorCraftGame {
+
+ private BlockWorld blockWorld;
+ private final int tileSize = 16;
+ private final int gridSize = 16;
+ private final int height = 32;
+ private Node root;
+ private Camera camera;
+ private PlayerBase player;
+
+ @Override
+ public void update(final ReadOnlyTimer timer) {
+ camera.setLocation(player.getPosition());
+ camera.setDirection(player.getDirection());
+ camera.setUp(player.getUp());
+ camera.setLeft(player.getLeft());
+
+ // The infinite world update
+ blockWorld.updatePlayer(player.getPosition(), player.getDirection());
+ blockWorld.update(timer);
+ }
+
+ @Override
+ public void render(final Renderer renderer) {
+ root.draw(renderer);
+ }
+
+ @Override
+ public void init(final Node root, final CanvasRelayer canvas, final LogicalLayer logicalLayer,
+ final PhysicalLayer physicalLayer, final MouseManager mouseManager) {
+ this.root = root;
+
+ // Make sure the world can find the textures.
+ try {
+ final SimpleResourceLocator srl = new SimpleResourceLocator(
+ ResourceLocatorTool.getClassPathResource(SimpleGame.class, "com/ardorcraft/resources"));
+ ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_TEXTURE, srl);
+ } catch (final URISyntaxException ex) {
+ ex.printStackTrace();
+ }
+
+ // Some window details
+ canvas.setTitle("ArdorCraft API Example - SimpleGame.java");
+ canvas.getCanvasRenderer().getRenderer().setBackgroundColor(ColorRGBA.WHITE);
+
+ // Setup camera fov and view distance
+ camera = canvas.getCanvasRenderer().getCamera();
+ camera.setFrustumPerspective(75.0, (float) camera.getWidth() / (float) camera.getHeight(), 0.1, 1000);
+
+ // Create player object
+ player = new PlayerBase();
+ player.getPosition().set(0, 30, 0);
+ FlyControl.setupTriggers(player, logicalLayer, Vector3.UNIT_Y, true);
+
+ // Create block world settings.
+ final WorldSettings settings = new WorldSettings();
+ settings.setTerrainTexture(
+ ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, "terrainQ.png"));
+ settings.setTerrainTextureTileSize(16);
+ settings.setWaterTexture(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, "water.png"));
+ settings.setTileSize(tileSize);
+ settings.setTileHeight(height);
+ settings.setGridSize(gridSize);
+ settings.setUseVBO(false);
+
+ // Create a local "fake" server
+ final IServerConnection serverConnection = new LocalServerConnection(
+ new LocalServerDataHandler(tileSize, height, gridSize, simpleSineGenerator, null));
+ settings.setServerConnection(serverConnection);
+
+ // Create the actual world and put its world node under our main scenegraph node.
+ blockWorld = new BlockWorld(settings);
+ root.attachChild(blockWorld.getWorldNode());
+
+ // Start the processing!
+ blockWorld.startThreads();
+ }
+
+ @Override
+ public void destroy() {
+ blockWorld.stopThreads();
+ }
+
+ @Override
+ public void resize(final int newWidth, final int newHeight) {
+ }
+
+ /**
+ * A simple world based on a sine wave.
+ */
+ private final LayerDataGenerator simpleSineGenerator = new LayerDataGenerator(1, 5) {
+ @Override
+ public boolean isCave(final int x, final int y, final int z, final WorldModifier blockScene) {
+ return y > 10 && y < 15;
+ }
+
+ @Override
+ public int getLayerType(final int layer, final int x, final int z, final WorldModifier blockScene) {
+ return MathUtils.rand.nextInt(4) + 1;
+ }
+
+ @Override
+ public int getLayerHeight(final int layer, final int x, final int y, final int z,
+ final WorldModifier blockScene) {
+ return (int) (Math.abs(Math.sin(x * 0.05) * Math.cos(z * 0.05)) * height);
+ }
+ };
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/RealGame.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/RealGame.java
new file mode 100644
index 0000000..fb72e19
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/RealGame.java
@@ -0,0 +1,475 @@
+/**
+ * Copyright (c) 2008-2010 Ardor Labs, Inc.
+ *
+ * This file is part of Ardor3D.
+ *
+ * Ardor3D is free software: you can redistribute it and/or modify it
+ * under the terms of its license which may be found in the accompanying
+ * LICENSE file or at <http://www.ardor3d.com/LICENSE>.
+ */
+
+package com.ardor3d.example.craft.examples.thegame;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.util.concurrent.Callable;
+import java.util.function.Predicate;
+
+import javax.swing.WindowConstants;
+
+import com.ardor3d.example.craft.base.ArdorCraftGame;
+import com.ardor3d.example.craft.base.CanvasRelayer;
+import com.ardor3d.example.craft.network.LocalServerConnection;
+import com.ardor3d.example.craft.network.LocalServerDataHandler;
+import com.ardor3d.example.craft.player.PlayerWithPhysics;
+import com.ardor3d.framework.Canvas;
+import com.ardor3d.input.GrabbedState;
+import com.ardor3d.input.Key;
+import com.ardor3d.input.MouseButton;
+import com.ardor3d.input.MouseManager;
+import com.ardor3d.input.PhysicalLayer;
+import com.ardor3d.input.logical.InputTrigger;
+import com.ardor3d.input.logical.KeyHeldCondition;
+import com.ardor3d.input.logical.KeyPressedCondition;
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.input.logical.MouseButtonPressedCondition;
+import com.ardor3d.input.logical.TriggerAction;
+import com.ardor3d.input.logical.TwoInputStates;
+import com.ardor3d.math.ColorRGBA;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.math.type.ReadOnlyColorRGBA;
+import com.ardor3d.renderer.Camera;
+import com.ardor3d.renderer.ContextManager;
+import com.ardor3d.renderer.Renderer;
+import com.ardor3d.renderer.queue.RenderBucketType;
+import com.ardor3d.renderer.state.BlendState;
+import com.ardor3d.renderer.state.FogState;
+import com.ardor3d.renderer.state.WireframeState;
+import com.ardor3d.scenegraph.Mesh;
+import com.ardor3d.scenegraph.Node;
+import com.ardor3d.scenegraph.hint.LightCombineMode;
+import com.ardor3d.scenegraph.hint.NormalsMode;
+import com.ardor3d.scenegraph.shape.Pyramid;
+import com.ardor3d.scenegraph.shape.Teapot;
+import com.ardor3d.ui.text.BasicText;
+import com.ardor3d.util.GameTaskQueue;
+import com.ardor3d.util.GameTaskQueueManager;
+import com.ardor3d.util.ReadOnlyTimer;
+import com.ardor3d.util.resource.ResourceLocatorTool;
+import com.ardor3d.util.resource.SimpleResourceLocator;
+import com.ardorcraft.collision.IntersectionResult;
+import com.ardorcraft.data.Pos;
+import com.ardorcraft.generators.DataGenerator;
+import com.ardorcraft.objects.QuadBox;
+import com.ardorcraft.objects.SkyDome;
+import com.ardorcraft.util.BlockUtil;
+import com.ardorcraft.util.geometryproducers.BoxProducer;
+import com.ardorcraft.util.geometryproducers.MeshProducer;
+import com.ardorcraft.voxel.Voxelator;
+import com.ardorcraft.world.BlockSide;
+import com.ardorcraft.world.BlockType;
+import com.ardorcraft.world.BlockWorld;
+import com.ardorcraft.world.IServerConnection;
+import com.ardorcraft.world.WorldSettings;
+
+/**
+ * A bigger example that will grow over time...
+ */
+public class RealGame implements ArdorCraftGame {
+
+ private BlockWorld blockWorld;
+ private final int tileSize = 16;
+ private final int height = 150;
+ private double farPlane = 10000.0;
+
+ private final IntersectionResult intersectionResult = new IntersectionResult();
+
+ private final FogState fogState = new FogState();
+ private final ColorRGBA fogColor = new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
+ private final ColorRGBA topColor = new ColorRGBA(0.5f, 0.6f, 1.0f, 1.0f);
+
+ private CanvasRelayer canvas;
+ private Node root;
+ private Camera camera;
+ private PlayerWithPhysics player;
+
+ private Node worldNode;
+ private Node textNode;
+ private SkyDome skyDome;
+ private QuadBox selectionBox;
+
+ private int blockType = 1;
+ private float globalLight = 1f;
+ private boolean isInWater = false;
+ private final int[] blockTypeLookup = new int[] { 1, 47, 4, 5, 20, 95, 12, 45, 48, 50 };
+
+ @Override
+ public void update(final ReadOnlyTimer timer) {
+ player.update(blockWorld, timer);
+
+ blockWorld.tracePicking(player.getPosition(), player.getDirection(), 50, intersectionResult);
+ if (intersectionResult.hit) {
+ final Pos hitPos = intersectionResult.pos;
+ selectionBox.setTranslation(hitPos.x + 0.5, hitPos.y + 0.5, hitPos.z + 0.5);
+ }
+
+ camera.setLocation(player.getPosition());
+ camera.setDirection(player.getDirection());
+ camera.setUp(player.getUp());
+ camera.setLeft(player.getLeft());
+
+ skyDome.setTranslation(player.getPosition());
+
+ updateFog(player.getPosition());
+
+ // The infinite world update
+ blockWorld.updatePlayer(player.getPosition(), player.getDirection());
+ blockWorld.update(timer);
+ }
+
+ @Override
+ public void render(final Renderer renderer) {
+ // root.draw(renderer);
+
+ // Taking over the drawing to draw in specific order without performance
+ // hogging renderqueue sorting...
+ skyDome.draw(renderer);
+ worldNode.draw(renderer);
+ if (intersectionResult.hit) {
+ selectionBox.draw(renderer);
+ }
+ textNode.draw(renderer);
+ }
+
+ @Override
+ public void init(final Node root, final CanvasRelayer canvas, final LogicalLayer logicalLayer,
+ final PhysicalLayer physicalLayer, final MouseManager mouseManager) {
+ this.root = root;
+ this.canvas = canvas;
+
+ try {
+ final SimpleResourceLocator srl = new SimpleResourceLocator(
+ ResourceLocatorTool.getClassPathResource(RealGame.class, "com/ardorcraft/resources"));
+ ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_TEXTURE, srl);
+ ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_MODEL, srl);
+ } catch (final URISyntaxException ex) {
+ ex.printStackTrace();
+ }
+
+ canvas.setTitle("ArdorCraft API Example - RealGame.java");
+
+ final SelectDialog dialog = new SelectDialog();
+ dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ dialog.setLocationRelativeTo(null);
+ dialog.setVisible(true);
+
+ DataGenerator dataGenerator = null;
+ try {
+ dataGenerator = (DataGenerator) dialog.getSelectedGenerator().newInstance();
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ final String texture = dialog.getSelectedTexture();
+ final int textureTileSize = dialog.getSelectedTextureSize();
+ final boolean doOverwriteMap = dialog.getIsOverwriteMap();
+ final int gridSize = dialog.getViewDistance();
+
+ farPlane = (gridSize - 1) / 2 * tileSize;
+
+ camera = canvas.getCanvasRenderer().getCamera();
+ camera.setFrustumPerspective(75.0, (float) camera.getWidth() / (float) camera.getHeight(), 0.1, farPlane);
+
+ setupFog();
+
+ // Create player object
+ player = new PlayerWithPhysics(logicalLayer);
+ player.getPosition().set(15, 50, 15);
+ player.setWalking(true);
+
+ registerTriggers(logicalLayer, mouseManager);
+
+ // Map file to use
+ final File worldFileSource = new File(dialog.getSelectedGenerator().getSimpleName() + "_Map.acr");
+ // Uncomment this if you want to start your mapfile from scratch each run...
+ if (doOverwriteMap && worldFileSource.exists()) {
+ worldFileSource.delete();
+ }
+
+ // Create main blockworld handler
+ final WorldSettings settings = new WorldSettings();
+
+ // Here you can load any terrain texture you wish (should contain 16x16 tiles).
+ // Just make sure you set the correct tilesize, that is, the subtexture size in pixels.
+ settings.setTerrainTexture(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, texture));
+ settings.setTerrainTextureTileSize(textureTileSize);
+
+ settings.setWaterTexture(ResourceLocatorTool.locateResource(ResourceLocatorTool.TYPE_TEXTURE, "water.png"));
+ settings.setTileSize(tileSize);
+ settings.setTileHeight(height);
+ settings.setGridSize(gridSize);
+
+ final IServerConnection serverConnection = new LocalServerConnection(
+ new LocalServerDataHandler(tileSize, height, gridSize, dataGenerator, worldFileSource));
+ settings.setServerConnection(serverConnection);
+
+ blockWorld = new BlockWorld(settings);
+
+ // Set block 45 (brickblock) to be a pyramid drawn with the meshproducer
+ final BlockUtil blockUtil = blockWorld.getBlockUtil();
+ final int blockId = 45;
+ blockUtil.setBlockMapping(blockId, 7, 0); // brick block tile coords
+ blockUtil.setBlockType(blockId, BlockType.Transparent); // Not covering the entire box block = not solid
+ final Mesh mesh = new Pyramid("pyramid", 1.0, 1.0);
+ final MeshProducer meshProducer = new MeshProducer(mesh);
+ meshProducer.createOrientations(); // create all permutation rotations of the mesh
+ meshProducer.setTransformTextureCoords(true); // transform 0-1 texcoords to the specific tile
+ blockUtil.setGeometryProducer(blockId, meshProducer);
+
+ worldNode = blockWorld.getWorldNode();
+ root.attachChild(worldNode);
+
+ skyDome = new SkyDome("Dome", 8, 8, 10);
+ root.attachChild(skyDome);
+
+ textNode = new Node("text");
+ root.attachChild(textNode);
+ createText("+", canvas.getCanvasRenderer().getCamera().getWidth() / 2 - 5,
+ canvas.getCanvasRenderer().getCamera().getHeight() / 2 - 10);
+ createText("[Y/H] Change time of day", 10, 10);
+ createText("[V] Voxelate a mesh at current target pos", 10, 30);
+ createText("[F] Fly/Walk", 10, 50);
+ createText("[0..9] Select blocktype (9=torch)", 10, 70);
+ createText("[LMB/RMB] Add/Remove block", 10, 90);
+
+ // Create box to show selected box
+ selectionBox = new QuadBox("SelectionBox", new Vector3(), 0.501, 0.501, 0.501);
+ selectionBox.getSceneHints().setNormalsMode(NormalsMode.Off);
+ selectionBox.setDefaultColor(new ColorRGBA(0.1f, 0.1f, 0.1f, 0.4f));
+ selectionBox.getSceneHints().setRenderBucketType(RenderBucketType.Skip);
+ final BlendState bs = new BlendState();
+ bs.setBlendEnabled(true);
+ selectionBox.setRenderState(bs);
+ final WireframeState ws = new WireframeState();
+ ws.setLineWidth(2);
+ selectionBox.setRenderState(ws);
+ selectionBox.getSceneHints().setLightCombineMode(LightCombineMode.Off);
+ root.attachChild(selectionBox);
+
+ updateLighting();
+
+ blockWorld.startThreads();
+ }
+
+ private void createText(final String text, final int x, final int y) {
+ final BasicText info = BasicText.createDefaultTextLabel("Text2", text, 16);
+ info.getSceneHints().setRenderBucketType(RenderBucketType.Ortho);
+ info.setTranslation(new Vector3(x, y, 0));
+ textNode.attachChild(info);
+ }
+
+ private void updateLighting() {
+ final float light = globalLight * 0.9f + 0.1f;
+ final ReadOnlyColorRGBA newColor = new ColorRGBA(fogColor).multiplyLocal(light);
+ fogState.setColor(newColor);
+ skyDome.getMidColor().set(newColor);
+ skyDome.getTopColor().set(topColor).multiplyLocal(light);
+ skyDome.updateColors();
+
+ GameTaskQueueManager.getManager(ContextManager.getCurrentContext()).getQueue(GameTaskQueue.RENDER)
+ .enqueue(new Callable<Boolean>() {
+ @Override
+ public Boolean call() throws Exception {
+ canvas.getCanvasRenderer().getRenderer().setBackgroundColor(newColor);
+ return true;
+ }
+ });
+ }
+
+ private void setupFog() {
+ fogState.setDensity(1.0f);
+ fogState.setEnabled(true);
+ fogState.setEnd((float) farPlane);
+ fogState.setStart((float) farPlane / 3.0f);
+ fogState.setDensityFunction(FogState.DensityFunction.Linear);
+ fogState.setQuality(FogState.Quality.PerPixel);
+ root.setRenderState(fogState);
+ }
+
+ private void updateFog(final Vector3 position) {
+ final int block = blockWorld.getBlock((int) position.getX(), (int) (position.getY() + 0.15),
+ (int) position.getZ());
+ if (block == BlockWorld.WATER && !isInWater) {
+ isInWater = true;
+ fogColor.set(0.1f, 0.13f, 0.25f, 1.0f);
+ topColor.set(0.15f, 0.2f, 0.35f, 1.0f);
+ fogState.setStart(0);
+ fogState.setEnd((float) farPlane / 8);
+ updateLighting();
+ } else if (block == 0 && isInWater) {
+ isInWater = false;
+ fogColor.set(1.0f, 1.0f, 1.0f, 1.0f);
+ topColor.set(0.5f, 0.6f, 1.0f, 1.0f);
+ fogState.setEnd((float) farPlane);
+ fogState.setStart((float) farPlane / 3.0f);
+ updateLighting();
+ }
+ }
+
+ private void registerTriggers(final LogicalLayer logicalLayer, final MouseManager mouseManager) {
+ logicalLayer.registerTrigger(
+ new InputTrigger(new MouseButtonPressedCondition(MouseButton.LEFT), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ addBlock();
+ }
+ }));
+
+ logicalLayer.registerTrigger(
+ new InputTrigger(new MouseButtonPressedCondition(MouseButton.RIGHT), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputStates, final double tpf) {
+ removeBlock();
+ }
+ }));
+
+ logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.F), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ player.setWalking(!player.isWalking());
+ }
+ }));
+
+ logicalLayer.registerTrigger(new InputTrigger(new KeyHeldCondition(Key.SPACE), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ player.jump();
+ }
+ }));
+
+ final Predicate<TwoInputStates> numberPressed = new Predicate<TwoInputStates>() {
+ @Override
+ public boolean test(final TwoInputStates states) {
+ final char keyChar = states.getCurrent().getKeyboardState().getKeyEvent().getKeyChar();
+ if (Character.isDigit(keyChar)) {
+ blockType = blockTypeLookup[Character.digit(keyChar, 10)];
+ return true;
+ }
+ return false;
+ }
+ };
+ logicalLayer.registerTrigger(new InputTrigger(numberPressed, new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ }
+ }));
+
+ logicalLayer.registerTrigger(new InputTrigger(new KeyHeldCondition(Key.Y), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ globalLight = (float) Math.min(globalLight + tpf * 0.4, 1);
+ blockWorld.setGlobalLight(globalLight);
+ updateLighting();
+ }
+ }));
+ logicalLayer.registerTrigger(new InputTrigger(new KeyHeldCondition(Key.H), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ globalLight = (float) Math.max(globalLight - tpf * 0.4, 0);
+ blockWorld.setGlobalLight(globalLight);
+ updateLighting();
+ }
+ }));
+
+ logicalLayer.registerTrigger(new InputTrigger(new KeyPressedCondition(Key.V), new TriggerAction() {
+ @Override
+ public void perform(final Canvas source, final TwoInputStates inputState, final double tpf) {
+ if (intersectionResult.hit) {
+ final Pos addPos = intersectionResult.oldPos;
+ final Voxelator voxelator = new Voxelator(blockWorld, 50, 50, 50);
+ voxelator.voxelate(addPos, new Teapot(), 1.0f, 43);
+ }
+ }
+ }));
+
+ if (mouseManager.isSetGrabbedSupported()) {
+ mouseManager.setGrabbed(GrabbedState.GRABBED);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ blockWorld.stopThreads();
+ }
+
+ @Override
+ public void resize(final int newWidth, final int newHeight) {
+ }
+
+ private void addBlock() {
+ if (intersectionResult.hit) {
+ final Pos addPos = intersectionResult.oldPos;
+ if (!player.isPlayerSpace(addPos)) {
+ final BlockSide orientation = getOrientation(blockType);
+ blockWorld.setBlock(addPos.x, addPos.y, addPos.z, blockType, orientation);
+ }
+ }
+ }
+
+ private void removeBlock() {
+ if (intersectionResult.hit) {
+ final Pos deletePos = intersectionResult.pos;
+ blockWorld.setBlock(deletePos.x, deletePos.y, deletePos.z, 0);
+
+ final int aboveBlock = blockWorld.getBlock(deletePos.x, deletePos.y + 1, deletePos.z);
+ if (aboveBlock > 99 && aboveBlock < 108) {
+ blockWorld.setBlock(deletePos.x, deletePos.y + 1, deletePos.z, 0);
+ }
+ }
+ }
+
+ private BlockSide getOrientation(final int blockId) {
+ BlockSide orientation = BlockSide.Front;
+
+ if (blockWorld.getBlockUtil().getGeometryProducer(blockId) instanceof BoxProducer) {
+ final Vector3 dir = player.getDirection();
+ if (Math.abs(dir.getX()) > Math.abs(dir.getZ())) {
+ if (dir.getX() < 0) {
+ orientation = BlockSide.Right;
+ } else {
+ orientation = BlockSide.Left;
+ }
+ } else {
+ if (dir.getZ() < 0) {
+ orientation = BlockSide.Front;
+ } else {
+ orientation = BlockSide.Back;
+ }
+ }
+ } else {
+ final int xDir = intersectionResult.oldPos.x - intersectionResult.pos.x;
+ final int yDir = intersectionResult.oldPos.y - intersectionResult.pos.y;
+ final int zDir = intersectionResult.oldPos.z - intersectionResult.pos.z;
+ if (xDir != 0) {
+ if (xDir > 0) {
+ orientation = BlockSide.Right;
+ } else {
+ orientation = BlockSide.Left;
+ }
+ } else if (yDir != 0) {
+ if (yDir > 0) {
+ orientation = BlockSide.Bottom;
+ } else {
+ orientation = BlockSide.Top;
+ }
+ } else if (zDir != 0) {
+ if (zDir > 0) {
+ orientation = BlockSide.Front;
+ } else {
+ orientation = BlockSide.Back;
+ }
+ }
+ }
+
+ return orientation;
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/RealGameApplication.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/RealGameApplication.java
new file mode 100644
index 0000000..0390e8d
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/RealGameApplication.java
@@ -0,0 +1,16 @@
+
+package com.ardor3d.example.craft.examples.thegame;
+
+import com.ardor3d.example.craft.base.ArdorBaseApplication;
+
+public class RealGameApplication extends ArdorBaseApplication {
+
+ public RealGameApplication() {
+ super(new RealGame());
+ }
+
+ public static void main(final String[] args) {
+ final ArdorBaseApplication example = new RealGameApplication();
+ new Thread(example, "MainArdorThread").start();
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/SelectDialog.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/SelectDialog.java
new file mode 100644
index 0000000..4083017
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/examples/thegame/SelectDialog.java
@@ -0,0 +1,189 @@
+
+package com.ardor3d.example.craft.examples.thegame;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.Frame;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.WindowConstants;
+import javax.swing.border.EmptyBorder;
+
+import com.ardor3d.example.craft.generators.InterpolatedNoiseDataGenerator;
+import com.ardor3d.example.craft.generators.NiceDataGenerator;
+
+public class SelectDialog extends JDialog {
+
+ private final JPanel contentPanel = new JPanel();
+
+ final Class[] generators = { NiceDataGenerator.class, InterpolatedNoiseDataGenerator.class };
+ final String[] textures = { "terrainQ.png", "terrain.png", "terrainDocu.png", "terrainSMP.png" };
+ final int[] tileSizes = { 16, 32, 32, 32 };
+
+ private JComboBox generatorCombo;
+
+ private JComboBox textureCombo;
+
+ private JSlider viewDistanceSpinner;
+
+ private JCheckBox overwriteCheckbox;
+
+ /**
+ * Launch the application.
+ */
+ public static void main(final String[] args) {
+ try {
+ final SelectDialog dialog = new SelectDialog();
+ dialog.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+ dialog.setVisible(true);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Create the dialog.
+ */
+ public SelectDialog() {
+ super((Frame) null, "Setup", true);
+
+ setAlwaysOnTop(true);
+
+ setBounds(100, 100, 331, 190);
+ getContentPane().setLayout(new BorderLayout());
+ contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
+ getContentPane().add(contentPanel, BorderLayout.CENTER);
+ final GridBagLayout gbl_contentPanel = new GridBagLayout();
+ gbl_contentPanel.columnWidths = new int[] { 0, 0, 0 };
+ gbl_contentPanel.rowHeights = new int[] { 0, 0, 0, 0, 0 };
+ gbl_contentPanel.columnWeights = new double[] { 0.0, 1.0, Double.MIN_VALUE };
+ gbl_contentPanel.rowWeights = new double[] { 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE };
+ contentPanel.setLayout(gbl_contentPanel);
+ {
+ final JLabel lblSelectTerrainGenerator = new JLabel("Terrain generator:");
+ final GridBagConstraints gbc_lblSelectTerrainGenerator = new GridBagConstraints();
+ gbc_lblSelectTerrainGenerator.insets = new Insets(0, 0, 5, 5);
+ gbc_lblSelectTerrainGenerator.anchor = GridBagConstraints.EAST;
+ gbc_lblSelectTerrainGenerator.gridx = 0;
+ gbc_lblSelectTerrainGenerator.gridy = 0;
+ contentPanel.add(lblSelectTerrainGenerator, gbc_lblSelectTerrainGenerator);
+ }
+ {
+ final String[] classNames = new String[generators.length];
+ for (int i = 0; i < generators.length; i++) {
+ classNames[i] = generators[i].getSimpleName();
+ }
+ generatorCombo = new JComboBox(classNames);
+ final GridBagConstraints gbc_comboBox = new GridBagConstraints();
+ gbc_comboBox.insets = new Insets(0, 0, 5, 0);
+ gbc_comboBox.fill = GridBagConstraints.HORIZONTAL;
+ gbc_comboBox.gridx = 1;
+ gbc_comboBox.gridy = 0;
+ contentPanel.add(generatorCombo, gbc_comboBox);
+ }
+ {
+ final JLabel lblSelectTerrainTexture = new JLabel("Terrain texture:");
+ final GridBagConstraints gbc_lblSelectTerrainTexture = new GridBagConstraints();
+ gbc_lblSelectTerrainTexture.anchor = GridBagConstraints.EAST;
+ gbc_lblSelectTerrainTexture.insets = new Insets(0, 0, 5, 5);
+ gbc_lblSelectTerrainTexture.gridx = 0;
+ gbc_lblSelectTerrainTexture.gridy = 1;
+ contentPanel.add(lblSelectTerrainTexture, gbc_lblSelectTerrainTexture);
+ }
+ {
+ textureCombo = new JComboBox(textures);
+ final GridBagConstraints gbc_comboBox = new GridBagConstraints();
+ gbc_comboBox.insets = new Insets(0, 0, 5, 0);
+ gbc_comboBox.fill = GridBagConstraints.HORIZONTAL;
+ gbc_comboBox.gridx = 1;
+ gbc_comboBox.gridy = 1;
+ contentPanel.add(textureCombo, gbc_comboBox);
+ }
+ {
+ final JLabel lblViewDistance = new JLabel("View distance:");
+ final GridBagConstraints gbc_lblViewDistance = new GridBagConstraints();
+ gbc_lblViewDistance.insets = new Insets(0, 0, 5, 5);
+ gbc_lblViewDistance.gridx = 0;
+ gbc_lblViewDistance.gridy = 2;
+ contentPanel.add(lblViewDistance, gbc_lblViewDistance);
+ }
+ {
+ viewDistanceSpinner = new JSlider(4, 40, 20);
+ viewDistanceSpinner.setPaintTicks(true);
+ viewDistanceSpinner.setSnapToTicks(true);
+ viewDistanceSpinner.setMinorTickSpacing(4);
+ viewDistanceSpinner.setMajorTickSpacing(4);
+ viewDistanceSpinner.setPaintLabels(true);
+ final GridBagConstraints gbc_spinner = new GridBagConstraints();
+ gbc_spinner.fill = GridBagConstraints.HORIZONTAL;
+ gbc_spinner.insets = new Insets(0, 0, 5, 0);
+ gbc_spinner.gridx = 1;
+ gbc_spinner.gridy = 2;
+ contentPanel.add(viewDistanceSpinner, gbc_spinner);
+ }
+ {
+ final JLabel lblOverwriteMap = new JLabel("Overwrite map:");
+ final GridBagConstraints gbc_lblOverwriteMap = new GridBagConstraints();
+ gbc_lblOverwriteMap.insets = new Insets(0, 0, 0, 5);
+ gbc_lblOverwriteMap.gridx = 0;
+ gbc_lblOverwriteMap.gridy = 3;
+ contentPanel.add(lblOverwriteMap, gbc_lblOverwriteMap);
+ }
+ {
+ overwriteCheckbox = new JCheckBox("");
+ final GridBagConstraints gbc_checkBox = new GridBagConstraints();
+ gbc_checkBox.anchor = GridBagConstraints.WEST;
+ gbc_checkBox.gridx = 1;
+ gbc_checkBox.gridy = 3;
+ contentPanel.add(overwriteCheckbox, gbc_checkBox);
+ }
+ {
+ final JPanel buttonPane = new JPanel();
+ buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT));
+ getContentPane().add(buttonPane, BorderLayout.SOUTH);
+ {
+ final JButton okButton = new JButton("OK");
+ okButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ SelectDialog.this.setVisible(false);
+ }
+ });
+ okButton.setActionCommand("OK");
+ buttonPane.add(okButton);
+ getRootPane().setDefaultButton(okButton);
+ }
+ }
+ }
+
+ public Class getSelectedGenerator() {
+ return generators[generatorCombo.getSelectedIndex()];
+ }
+
+ public String getSelectedTexture() {
+ return textures[textureCombo.getSelectedIndex()];
+ }
+
+ public int getSelectedTextureSize() {
+ return tileSizes[textureCombo.getSelectedIndex()];
+ }
+
+ public int getViewDistance() {
+ return viewDistanceSpinner.getValue();
+ }
+
+ public boolean getIsOverwriteMap() {
+ return overwriteCheckbox.isSelected();
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/GeneratorViewer.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/GeneratorViewer.java
new file mode 100644
index 0000000..35784df
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/GeneratorViewer.java
@@ -0,0 +1,334 @@
+
+package com.ardor3d.example.craft.generators;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.image.BufferedImage;
+import java.awt.image.DataBufferInt;
+import java.util.Random;
+import java.util.Stack;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.SpinnerModel;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.SwingUtilities;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import com.ardor3d.math.MathUtils;
+import com.ardorcraft.generators.DataGenerator;
+import com.ardorcraft.world.WorldModifier;
+
+/**
+ * Use this to play around more easily with your terrain generators.
+ *
+ */
+public class GeneratorViewer {
+ private static final Logger logger = Logger.getLogger(GeneratorViewer.class.getName());
+
+ public static void main(final String[] args) {
+ new GeneratorViewer(new InterpolatedNoiseDataGenerator(), 32, 150, 16);
+ }
+
+ private int spacing = 1;
+
+ private final Color[][] colors;
+
+ private final int chunkWidth;
+ private final int chunkHeight;
+ private final int gridSize;
+ private final int totalSize;
+
+ private int offsetX = 0;
+ private int offsetZ = 0;
+
+ private final BufferedImage image;
+ private final int[] data;
+ private final JPanel panel;
+ private final JButton button;
+ private final JSpinner spinner;
+
+ private int nrUpdateThreads = 1;
+ private final ExecutorService executorService = Executors.newCachedThreadPool(new DeamonThreadFactory());
+ private final Stack<Future<?>> futureStack = new Stack<Future<?>>();
+
+ private class PaintPanel extends JPanel {
+ @Override
+ public void paint(final Graphics g) {
+ g.drawImage(image, 0, 0, null);
+ }
+ }
+
+ private final DataGenerator generator;
+
+ private final ThreadLocal<WorldModifier> worldModifierPool = new ThreadLocal<WorldModifier>() {
+ @Override
+ protected WorldModifier initialValue() {
+ return new LocalBlockModifier(new byte[chunkWidth * chunkWidth * chunkHeight]);
+ }
+ };
+
+ public GeneratorViewer(final DataGenerator generator, final int chunkWidth, final int chunkHeight,
+ final int gridSize) {
+ this.generator = generator;
+ this.chunkWidth = chunkWidth;
+ this.chunkHeight = chunkHeight;
+ this.gridSize = gridSize;
+
+ nrUpdateThreads = Runtime.getRuntime().availableProcessors();
+ logger.info("Running on " + nrUpdateThreads + " core" + (nrUpdateThreads > 1 ? "s" : ""));
+
+ totalSize = chunkWidth * gridSize;
+
+ panel = new PaintPanel();
+
+ final JPanel controlPanel = new JPanel();
+ button = new JButton("Render");
+ controlPanel.add(button);
+ button.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ render();
+ }
+ });
+ final SpinnerModel model = new SpinnerNumberModel(1, 1, 100, 1);
+ spinner = new JSpinner(model);
+ spinner.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(final ChangeEvent e) {
+ spacing = 1 << ((Integer) model.getValue()).intValue() - 1;
+ }
+ });
+ controlPanel.add(spinner);
+
+ JButton button1;
+
+ button1 = new JButton("Left");
+ controlPanel.add(button1);
+ button1.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ offsetX -= 16;
+ render();
+ }
+ });
+ button1 = new JButton("Right");
+ controlPanel.add(button1);
+ button1.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ offsetX += 16;
+ render();
+ }
+ });
+ button1 = new JButton("Up");
+ controlPanel.add(button1);
+ button1.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ offsetZ += 16;
+ render();
+ }
+ });
+ button1 = new JButton("Down");
+ controlPanel.add(button1);
+ button1.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(final ActionEvent e) {
+ offsetZ -= 16;
+ render();
+ }
+ });
+
+ final JFrame frame = new JFrame("Terrain Cache Debug");
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ frame.getContentPane().add(panel);
+ frame.getContentPane().add(controlPanel, BorderLayout.SOUTH);
+ frame.setBounds(10, 10, totalSize + 17, totalSize + 78);
+ frame.setVisible(true);
+
+ final Random rand = new Random(1337);
+ colors = new Color[255][chunkHeight];
+ for (int i = 0; i < 255; i++) {
+ float r = rand.nextFloat() * 0.5f + 0.5f;
+ float g = rand.nextFloat() * 0.5f + 0.5f;
+ float b = rand.nextFloat() * 0.5f + 0.5f;
+
+ for (int j = chunkHeight - 1; j >= 0; j--) {
+ colors[i][j] = new Color(r, g, b);
+ r *= 1.0f - 1.0f / chunkHeight;
+ g *= 1.0f - 1.0f / chunkHeight;
+ b *= 1.0f - 1.0f / chunkHeight;
+ }
+ }
+
+ image = new BufferedImage(totalSize, totalSize, BufferedImage.TYPE_INT_RGB);
+ data = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
+
+ render();
+ }
+
+ public void render() {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ button.setEnabled(false);
+ spinner.setEnabled(false);
+ }
+ });
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ if (nrUpdateThreads <= 1) {
+ updateChunks(0, gridSize);
+ } else {
+ for (int i = 0; i < nrUpdateThreads; i++) {
+ final int from = gridSize * i / nrUpdateThreads;
+ final int to = gridSize * (i + 1) / nrUpdateThreads;
+ final Future<?> future = executorService.submit(new Runnable() {
+ public void run() {
+ updateChunks(from, to);
+ }
+ });
+ futureStack.push(future);
+ }
+ try {
+ while (!futureStack.isEmpty()) {
+ futureStack.pop().get();
+ }
+ } catch (final InterruptedException ex) {
+ logger.log(Level.SEVERE, "InterruptedException in thread execution", ex);
+ } catch (final ExecutionException ex) {
+ logger.log(Level.SEVERE, "ExecutionException in thread execution", ex);
+ }
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ button.setEnabled(true);
+ spinner.setEnabled(true);
+ }
+ });
+ }
+ }).start();
+ }
+
+ private void updateChunks(final int from, final int to) {
+ final WorldModifier worldEdit = worldModifierPool.get();
+
+ for (int x = from * spacing; x < to * spacing; x++) {
+ for (int z = 0; z < gridSize * spacing; z++) {
+ final int xx = x + offsetX;
+ final int zz = z + offsetZ;
+ generator.generateChunk(xx * chunkWidth, zz * chunkWidth, xx * chunkWidth + chunkWidth,
+ zz * chunkWidth + chunkWidth, spacing, chunkHeight, worldEdit);
+ paintChunk(MathUtils.moduloPositive(xx, gridSize), MathUtils.moduloPositive(zz, gridSize), worldEdit);
+ panel.repaint();
+
+ Thread.yield();
+ }
+ }
+ }
+
+ private void paintChunk(final int xx, final int zz, final WorldModifier worldEdit) {
+ for (int x = 0; x < chunkWidth / spacing; x++) {
+ for (int z = 0; z < chunkWidth / spacing; z++) {
+ boolean light = true;
+ boolean empty = true;
+ for (int y = chunkHeight - 1; y >= 0; y--) {
+ final int blockId = worldEdit.getBlock(x * spacing, y, z * spacing);
+ if (blockId != 0) {
+ final int xpos = xx * chunkWidth / spacing + x;
+ final int ypos = zz * chunkWidth / spacing + z - y;
+ if (ypos >= 0) {
+ data[ypos * totalSize + xpos] = colors[blockId][y].getRGB();
+ if (empty && ypos - 1 >= 0) {
+ int yval = light ? y + 15 : y;
+ yval = empty ? yval + 30 : yval;
+ yval = Math.min(chunkHeight - 1, yval);
+ data[(ypos - 1) * totalSize + xpos] = colors[blockId][yval].getRGB();
+ }
+ }
+ light = false;
+ empty = false;
+ } else {
+ if (y == 0) {
+ final int xpos = xx * chunkWidth / spacing + x;
+ final int ypos = zz * chunkWidth / spacing + z;
+ data[ypos * totalSize + xpos] = Color.black.getRGB();
+ }
+ empty = true;
+ }
+ }
+ }
+ }
+ }
+
+ class LocalBlockModifier implements WorldModifier {
+ private final byte[] localBlock;
+
+ public LocalBlockModifier(final byte[] localBlock) {
+ this.localBlock = localBlock;
+ }
+
+ @Override
+ public void setBlock(final int x, final int y, final int z, final int data) {
+ if (y < 0 || y >= chunkHeight - 1) {
+ return;
+ }
+ final int xx = MathUtils.moduloPositive(x, chunkWidth);
+ final int zz = MathUtils.moduloPositive(z, chunkWidth);
+ localBlock[xx + (y + zz * chunkHeight) * chunkWidth] = (byte) data;
+ }
+
+ @Override
+ public int getBlock(final int x, final int y, final int z) {
+ if (y < 0 || y >= chunkHeight - 1) {
+ return 0;
+ }
+ final int xx = MathUtils.moduloPositive(x, chunkWidth);
+ final int zz = MathUtils.moduloPositive(z, chunkWidth);
+ return localBlock[xx + (y + zz * chunkHeight) * chunkWidth] & 0xff;
+ }
+ }
+
+ private static class DeamonThreadFactory implements ThreadFactory {
+ static final AtomicInteger poolNumber = new AtomicInteger(1);
+ final ThreadGroup group;
+ final AtomicInteger threadNumber = new AtomicInteger(1);
+ final String namePrefix;
+
+ DeamonThreadFactory() {
+ final SecurityManager s = System.getSecurityManager();
+ group = s != null ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
+ namePrefix = "Worker Pool-" + poolNumber.getAndIncrement() + "-thread-";
+ }
+
+ public Thread newThread(final Runnable r) {
+ final Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
+ if (!t.isDaemon()) {
+ t.setDaemon(true);
+ }
+ if (t.getPriority() != Thread.NORM_PRIORITY) {
+ t.setPriority(Thread.NORM_PRIORITY);
+ }
+ return t;
+ }
+ }
+
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/InterpolatedNoiseDataGenerator.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/InterpolatedNoiseDataGenerator.java
new file mode 100644
index 0000000..84fe0dd
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/InterpolatedNoiseDataGenerator.java
@@ -0,0 +1,236 @@
+
+package com.ardor3d.example.craft.generators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import com.ardor3d.math.MathUtils;
+import com.ardorcraft.data.Pos;
+import com.ardorcraft.generators.DataGenerator;
+import com.ardorcraft.util.ImprovedNoise;
+import com.ardorcraft.world.BlockWorld;
+import com.ardorcraft.world.WorldModifier;
+
+/**
+ * Some nice terrain with trees.
+ */
+public class InterpolatedNoiseDataGenerator implements DataGenerator {
+
+ private final double scale = 8.0;
+
+ @Override
+ public void generateChunk(final int xStart, final int zStart, final int xEnd, final int zEnd, final int spacing,
+ final int height, final WorldModifier blockScene) {
+
+ final int waterHeight = height / 3;
+ final double noiceCache[][][] = new double[2][2][2];
+
+ final List<Pos> treePositions = new ArrayList<>();
+ final List<Integer> treeHeights = new ArrayList<>();
+ final Random rand = new Random(xStart * 10000 + zStart);
+
+ for (int x = xStart; x < xEnd; x += spacing * scale) {
+ for (int y = 0; y < height; y += spacing * scale) {
+ for (int z = zStart; z < zEnd; z += spacing * scale) {
+
+ for (int xx = 0; xx < 2; xx++) {
+ for (int zz = 0; zz < 2; zz++) {
+ for (int yy = 0; yy < 2; yy++) {
+ noiceCache[xx][yy][zz] = noisePoint(x + xx * spacing * scale, y + yy * spacing * scale,
+ z + zz * spacing * scale, 1.0);
+ }
+ }
+ }
+
+ for (int xx = 0; xx < spacing * scale; xx++) {
+ for (int yy = 0; yy < spacing * scale; yy++) {
+ for (int zz = 0; zz < spacing * scale; zz++) {
+ final double noise = interpolatedNoise(noiceCache, xx, yy, zz) * 1.8 + 0.5;
+
+ final double heightval = (double) (y + yy) / (double) height;
+ final double testval = Math.sin(heightval * MathUtils.HALF_PI);
+ final double noiseTest = MathUtils.clamp(noise, 0.05, 0.95);
+ if (testval < noiseTest) {
+ int type = 1;
+ if (noiseTest - testval > 0.3) {
+ type = 7;
+ }
+
+ blockScene.setBlock(x + xx, y + yy, z + zz, type);
+ } else {
+ if (y + yy < waterHeight) {
+ blockScene.setBlock(x + xx, y + yy, z + zz, BlockWorld.WATER);
+ } else {
+ blockScene.setBlock(x + xx, y + yy, z + zz, 0);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (int x = xStart; x < xEnd; x += spacing) {
+ for (int z = zStart; z < zEnd; z += spacing) {
+
+ boolean top = true;
+ int blockId = 0;
+ int topHeight = height - 1;
+ while (topHeight > waterHeight - 5) {
+ for (; topHeight > 0; topHeight--) {
+ blockId = blockScene.getBlock(x, topHeight, z);
+
+ if (blockId != 0 && blockId != BlockWorld.WATER) {
+ break;
+ }
+ }
+ topHeight++;
+
+ if (topHeight > waterHeight - 5) {
+ final double noise1 = ImprovedNoise.noise(x * 0.1, 0, z * 0.1) * 0.5 + 0.4;
+ final double noise2 = ImprovedNoise.noise(x * 0.01, 0, z * 0.01) * 0.5 + 0.4;
+ final int h = (int) (noise1 * noise2 * 10);
+
+ if (topHeight < waterHeight) {
+ for (int y = 0; y < h; y++) {
+ blockScene.setBlock(x, topHeight + y, z, 12);
+ }
+ blockScene.setBlock(x, topHeight + h, z, 12);
+ } else {
+ for (int y = 0; y < h; y++) {
+ blockScene.setBlock(x, topHeight + y, z, 3);
+ }
+
+ if (top) {
+ blockScene.setBlock(x, topHeight + h, z, 2);
+
+ final boolean addedTree = checkAddTree(x, z, xStart, zStart, xEnd, zEnd,
+ topHeight + h + 1, treePositions, treeHeights, rand);
+
+ if (!addedTree) {
+ final double noiseVegetation = ImprovedNoise.noise(x * 0.4, 0, z * 0.4)
+ * (Math.abs(noise2) - 0.2) * 2.0;
+ if (noiseVegetation > (double) (topHeight + h - waterHeight) / height) {
+ blockScene.setBlock(x, topHeight + h + 1, z, 100 + MathUtils.rand.nextInt(8));
+ }
+ }
+ top = false;
+ }
+ }
+ }
+
+ topHeight--;
+ for (; topHeight > 0; topHeight--) {
+ blockId = blockScene.getBlock(x, topHeight, z);
+
+ if (blockId == 0) {
+ break;
+ }
+ }
+
+ }
+ }
+ }
+
+ int index = 0;
+ for (final Pos pos : treePositions) {
+ final int treeHeight = treeHeights.get(index++);
+ addTree(blockScene, pos, treeHeight, rand);
+ }
+ }
+
+ private double noisePoint(final double x, final double y, final double z, final double ySeed) {
+ final double noise1 = ImprovedNoise.noise(x * 0.001, y * 0.002 + ySeed, z * 0.001);
+ final double noise2 = ImprovedNoise.noise(x * 0.003, y * 0.006 + ySeed * 2, z * 0.003);
+ final double noise3 = ImprovedNoise.noise(x * 0.009, y * 0.018 + ySeed * 3, z * 0.009);
+ final double noise4 = ImprovedNoise.noise(x * 0.027, y * 0.054 + ySeed * 4, z * 0.027);
+ final double noise5 = ImprovedNoise.noise(x * 0.054, y * 0.1 + ySeed * 4, z * 0.054);
+
+ double noise = 0.0;
+ noise += noise1;
+ noise += noise2 * 0.9;
+ noise += noise3 * 0.8;
+ noise += noise4 * 0.7;
+ noise += noise5 * 0.6;
+
+ noise /= 4.0;
+
+ return noise;
+ }
+
+ private double interpolatedNoise(final double[][][] noiseCache, final int xTest, final int yTest, final int zTest) {
+ final int xx = 0;
+ final int yy = 0;
+ final int zz = 0;
+
+ final double x = xTest / scale;
+ final double y = yTest / scale;
+ final double z = zTest / scale;
+
+ final double v000 = noiseCache[xx][yy][zz];
+ final double v100 = noiseCache[xx + 1][yy][zz];
+ final double v010 = noiseCache[xx][yy + 1][zz];
+ final double v001 = noiseCache[xx][yy][zz + 1];
+ final double v101 = noiseCache[xx + 1][yy][zz + 1];
+ final double v011 = noiseCache[xx][yy + 1][zz + 1];
+ final double v110 = noiseCache[xx + 1][yy + 1][zz];
+ final double v111 = noiseCache[xx + 1][yy + 1][zz + 1];
+
+ final double Vxyz = v000 * (1.0 - x) * (1.0 - y) * (1.0 - z) + //
+ v100 * x * (1.0 - y) * (1.0 - z) + //
+ v010 * (1.0 - x) * y * (1.0 - z) + //
+ v001 * (1.0 - x) * (1.0 - y) * z + //
+ v101 * x * (1.0 - y) * z + //
+ v011 * (1.0 - x) * y * z + //
+ v110 * x * y * (1.0 - z) + //
+ v111 * x * y * z;
+ return Vxyz;
+ }
+
+ private final int[] height = new int[] { 5, 7, 9 };
+
+ private boolean checkAddTree(final int x, final int z, final int xStart, final int zStart, final int xEnd,
+ final int zEnd, final int localHeight, final List<Pos> treePositions, final List<Integer> treeHeights,
+ final Random rand) {
+ final int treeHeight = height[rand.nextInt(height.length)];
+ final int testHeight = (treeHeight - 1) / 2;
+
+ if (x >= xStart + testHeight && x < xEnd - testHeight && z >= zStart + testHeight && z < zEnd - testHeight) {
+ final double noiseTree = (ImprovedNoise.noise(x * 0.01, localHeight * 0.005, z * 0.01) + 0.3) * 0.2;
+ final double r = rand.nextDouble();
+ if (noiseTree > r) {
+ treePositions.add(new Pos(x, localHeight, z));
+ treeHeights.add(treeHeight);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void addTree(final WorldModifier blockScene, final Pos pos, final int treeHeight, final Random rand) {
+ for (int y = 0; y < treeHeight; y++) {
+ blockScene.setBlock(pos.x, pos.y + y, pos.z, 17);
+ }
+
+ for (int x = 0; x < treeHeight; x++) {
+ for (int z = 0; z < treeHeight; z++) {
+ for (int y = 0; y < treeHeight; y++) {
+ final int xx = x - (treeHeight - 1) / 2;
+ final int yy = y - (treeHeight - 1) / 2;
+ final int zz = z - (treeHeight - 1) / 2;
+ if (xx == 0 && zz == 0 && yy <= 0) {
+ continue;
+ }
+ final double test = MathUtils.sqrt((double) xx * xx + yy * yy + zz * zz);
+ if (test < (treeHeight - 1.0) / 2.0) {
+ if (rand.nextDouble() < 0.8) {
+ blockScene.setBlock(pos.x + xx, pos.y + yy + treeHeight - 1, pos.z + zz, 18);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/LayerDataGenerator.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/LayerDataGenerator.java
new file mode 100644
index 0000000..c98ec44
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/LayerDataGenerator.java
@@ -0,0 +1,59 @@
+
+package com.ardor3d.example.craft.generators;
+
+import com.ardorcraft.generators.DataGenerator;
+import com.ardorcraft.world.BlockWorld;
+import com.ardorcraft.world.WorldModifier;
+
+public abstract class LayerDataGenerator implements DataGenerator {
+ private final int nrLayers;
+ protected int waterHeight;
+
+ public LayerDataGenerator(final int nrLayers, final int waterHeight) {
+ this.nrLayers = nrLayers;
+ this.waterHeight = waterHeight;
+ }
+
+ @Override
+ public void generateChunk(final int xStart, final int zStart, final int xEnd, final int zEnd, final int spacing,
+ final int height, final WorldModifier blockScene) {
+ for (int x = xStart; x < xEnd; x++) {
+ for (int z = zStart; z < zEnd; z++) {
+ generateColumn(x, z, height, blockScene);
+ }
+ }
+ }
+
+ private void generateColumn(final int x, final int z, final int height, final WorldModifier blockScene) {
+ int startHeight = 1;
+ blockScene.setBlock(x, 0, z, 4);
+ for (int i = 0; i < nrLayers; i++) {
+ final int localHeight = Math.max(0, getLayerHeight(i, x, startHeight, z, blockScene));
+ final int type = getLayerType(i, x, z, blockScene);
+
+ for (int y = startHeight; y < startHeight + localHeight && y < height; y++) {
+ if (!isCave(x, y, z, blockScene)) {
+ blockScene.setBlock(x, y, z, type);
+ } else if (y < waterHeight) {
+ blockScene.setBlock(x, y, z, BlockWorld.WATER);
+ } else {
+ blockScene.setBlock(x, y, z, 0);
+ }
+ }
+ startHeight += localHeight;
+ }
+ for (int y = startHeight; y < height; y++) {
+ if (y < waterHeight) {
+ blockScene.setBlock(x, y, z, BlockWorld.WATER);
+ } else {
+ blockScene.setBlock(x, y, z, 0);
+ }
+ }
+ }
+
+ public abstract boolean isCave(int x, int y, int z, WorldModifier blockScene);
+
+ public abstract int getLayerType(int layer, int x, int z, WorldModifier blockScene);
+
+ public abstract int getLayerHeight(int layer, int x, int y, int z, WorldModifier blockScene);
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/NiceDataGenerator.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/NiceDataGenerator.java
new file mode 100644
index 0000000..43a3dcf
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/generators/NiceDataGenerator.java
@@ -0,0 +1,215 @@
+
+package com.ardor3d.example.craft.generators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import com.ardor3d.math.MathUtils;
+import com.ardorcraft.data.Pos;
+import com.ardorcraft.generators.DataGenerator;
+import com.ardorcraft.util.ImprovedNoise;
+import com.ardorcraft.world.BlockWorld;
+import com.ardorcraft.world.WorldModifier;
+
+/**
+ * Some nice terrain with trees.
+ */
+public class NiceDataGenerator implements DataGenerator {
+ private final int waterHeight = 20;
+
+ @Override
+ public void generateChunk(final int xStart, final int zStart, final int xEnd, final int zEnd, final int spacing,
+ final int height, final WorldModifier blockScene) {
+
+ final List<Pos> treePositions = new ArrayList<>();
+ final List<Integer> treeHeights = new ArrayList<>();
+ final Random rand = new Random(xStart * 10000 + zStart);
+
+ for (int x = xStart; x < xEnd; x += spacing) {
+ for (int z = zStart; z < zEnd; z += spacing) {
+ generateColumn(x, z, height, blockScene, xStart, zStart, xEnd, zEnd, treePositions, treeHeights, rand);
+ }
+ }
+
+ int index = 0;
+ for (final Pos pos : treePositions) {
+ final int treeHeight = treeHeights.get(index++);
+ addTree(blockScene, pos, treeHeight, rand);
+ }
+ }
+
+ private final int[] height = new int[] { 5, 7, 9 };
+
+ private void addTree(final WorldModifier blockScene, final Pos pos, final int treeHeight, final Random rand) {
+ for (int y = 0; y < treeHeight; y++) {
+ blockScene.setBlock(pos.x, pos.y + y, pos.z, 17);
+ }
+
+ for (int x = 0; x < treeHeight; x++) {
+ for (int z = 0; z < treeHeight; z++) {
+ for (int y = 0; y < treeHeight; y++) {
+ final int xx = x - (treeHeight - 1) / 2;
+ final int yy = y - (treeHeight - 1) / 2;
+ final int zz = z - (treeHeight - 1) / 2;
+ if (xx == 0 && zz == 0 && yy <= 0) {
+ continue;
+ }
+ final double test = MathUtils.sqrt((double) xx * xx + yy * yy + zz * zz);
+ if (test < (treeHeight - 1.0) / 2.0) {
+ if (rand.nextDouble() < 0.8) {
+ blockScene.setBlock(pos.x + xx, pos.y + yy + treeHeight - 1, pos.z + zz, 18);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void generateColumn(final int x, final int z, final int height, final WorldModifier blockScene,
+ final int xStart, final int zStart, final int xEnd, final int zEnd, final List<Pos> treePositions,
+ final List<Integer> treeHeights, final Random rand) {
+ final double gen = 5;
+
+ int localHeight = 0;
+
+ localHeight = generateLayer(x, z, gen + 0, 0.1, 0, 0.4f * height, 7, 0.2f, height, blockScene, rand);
+ localHeight = generateLayer(x, z, gen + 0.2, 1.5, localHeight, 0.08f * height + 1.5f * (localHeight - 10), 1,
+ 0.8f, height, blockScene, rand);
+ localHeight = generateLayer(x, z, gen + 0.5, 2.0, localHeight, 0.06f * height + 0.3f * (localHeight - 5), 3,
+ 0.6f, height, blockScene, rand);
+
+ // mountain
+ final double noise1 = ImprovedNoise.noise(x * 0.01, 20, z * 0.01) + 0.5;
+ final double noise3 = ImprovedNoise.noise(x * 0.05, 20, z * 0.05) + 0.5;
+ final double noise2 = ImprovedNoise.noise(x * 0.05, 100, z * 0.05);
+ double mul = (localHeight + height / 2.0) / height;
+ mul = 10.0 * MathUtils.clamp(mul, 0.0, 1.0);
+ int val = (int) (mul * noise1 * noise3 * (noise2 > 0.2 ? 1.0 : 0.0));
+ val = Math.max(0, val);
+ int type = 1;
+ for (int y = localHeight; y < localHeight + val; y++) {
+ if (y <= waterHeight + 1) {
+ type = 12;
+ }
+ final double scaleY = (Math.abs(y - height / 5) + 10.0) / height * 3.5;
+ final double scale = 0.05;
+ final double noise4 = ImprovedNoise.noise(x * scale, y * scale * 2.5, z * scale);
+ if (noise4 < scaleY) {
+ if (rand.nextDouble() < 0.1) {
+ blockScene.setBlock(x, y, z, 13);
+ } else {
+ blockScene.setBlock(x, y, z, type);
+ }
+ } else {
+ blockScene.setBlock(x, y, z, 0);
+ }
+ }
+ localHeight += val;
+
+ // sediment
+ final int block = blockScene.getBlock(x, localHeight - 1, z);
+ if (block == 3) {
+ if (localHeight - 1 <= waterHeight + 1) {
+ blockScene.setBlock(x, localHeight - 1, z, 12);
+ } else {
+ blockScene.setBlock(x, localHeight - 1, z, 2);
+ final boolean addedTree = checkAddTree(x, z, xStart, zStart, xEnd, zEnd, localHeight, treePositions,
+ treeHeights, rand);
+
+ final double noiseVegetation = ImprovedNoise.noise(x * 0.5, 0, z * 0.5) * Math.abs(noise3);
+ if (!addedTree && noiseVegetation > (double) (localHeight - waterHeight) / height) {
+ blockScene.setBlock(x, localHeight, z, 100 + MathUtils.rand.nextInt(8));
+ localHeight++;
+ }
+ }
+
+ if (noise2 < -0.4) {
+ final double noiseTree = ImprovedNoise.noise(x * 0.2, localHeight * 0.2, z * 0.2);
+ if (noiseTree > 0.4) {
+ final int mountainHeight = (int) ((noiseTree - 0.4) * 10);
+ for (int y = localHeight; y < localHeight + mountainHeight; y++) {
+ blockScene.setBlock(x, y, z, 1);
+ }
+ localHeight += mountainHeight;
+ }
+ }
+ }
+
+ if (localHeight < waterHeight) {
+ for (; localHeight < waterHeight; localHeight++) {
+ blockScene.setBlock(x, localHeight, z, BlockWorld.WATER);
+ }
+ }
+
+ for (int y = localHeight; y < height; y++) {
+ blockScene.setBlock(x, y, z, 0);
+ }
+ }
+
+ private boolean checkAddTree(final int x, final int z, final int xStart, final int zStart, final int xEnd,
+ final int zEnd, final int localHeight, final List<Pos> treePositions, final List<Integer> treeHeights,
+ final Random rand) {
+ final int treeHeight = height[rand.nextInt(height.length)];
+ final int testHeight = (treeHeight - 1) / 2;
+
+ if (x >= xStart + testHeight && x < xEnd - testHeight && z >= zStart + testHeight && z < zEnd - testHeight) {
+ final double noiseTree = (ImprovedNoise.noise(x * 0.01, localHeight * 0.005, z * 0.01) + 0.3) * 0.2;
+ final double r = rand.nextDouble();
+ if (noiseTree > r) {
+ treePositions.add(new Pos(x, localHeight, z));
+ treeHeights.add(treeHeight);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int generateLayer(final int x, final int z, final double noiseVal, final double noiseScale,
+ final int startheight, float layerheight, final int type, final float adder, final int height,
+ final WorldModifier blockScene, final Random rand) {
+ layerheight = Math.max(0.0f, layerheight);
+
+ double noise = ImprovedNoise.noise(x * 0.01 * noiseScale, noiseVal, z * 0.01 * noiseScale) + adder;
+ double noise2 = ImprovedNoise.noise(x * 0.05 * noiseScale, noiseVal, z * 0.05 * noiseScale) + adder;
+
+ double phatnoise = ImprovedNoise.noise(x * 0.004, noiseVal, z * 0.004);
+ phatnoise = MathUtils.clamp(Math.abs(phatnoise) + 0.6, 0.0, 1.0);
+ noise2 *= phatnoise;
+ noise *= phatnoise;
+
+ int localHeight = (int) (noise * layerheight + noise2 * layerheight * 0.35);
+ localHeight = Math.max(0, localHeight);
+
+ for (int y = startheight; y < startheight + localHeight; y++) {
+ if (y <= 1) {
+ blockScene.setBlock(x, y, z, 12);
+ }
+ final double scaleY = (Math.abs(y - height / 3) + 15.0) / height * 1.5;
+ final double scale = 0.05;
+ final double noise3 = ImprovedNoise.noise(x * scale, y * scale * 2.0, z * scale);
+ if (noise3 < scaleY) {
+ if (type == 1) {
+ final int r = rand.nextInt(100);
+ if (r < 3) {
+ blockScene.setBlock(x, y, z, 14 + r);
+ } else {
+ blockScene.setBlock(x, y, z, type);
+ }
+ } else {
+ blockScene.setBlock(x, y, z, type);
+ }
+ } else if (noise3 - scaleY < 0.02) {
+ blockScene.setBlock(x, y, z, 1);
+ } else {
+ if (y < waterHeight) {
+ blockScene.setBlock(x, y, z, BlockWorld.WATER);
+ } else {
+ blockScene.setBlock(x, y, z, 0);
+ }
+ }
+ }
+
+ return startheight + localHeight;
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/network/LocalServerConnection.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/network/LocalServerConnection.java
new file mode 100644
index 0000000..07c2733
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/network/LocalServerConnection.java
@@ -0,0 +1,117 @@
+
+package com.ardor3d.example.craft.network;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import com.ardor3d.math.type.ReadOnlyVector3;
+import com.ardorcraft.world.BlockSide;
+import com.ardorcraft.world.Chunk;
+import com.ardorcraft.world.ChunkModifier;
+import com.ardorcraft.world.IServerConnection;
+
+/**
+ * "Fake" local server implementation which just generate data upon request throught the LocalServerDataHandler and
+ * pongs the data back. For real server/client communication, just implement the IServerConnection and send the requests
+ * etc to your server.
+ */
+public class LocalServerConnection implements IServerConnection {
+ private final LocalServerDataHandler server;
+ private ChunkModifier chunkModifier;
+
+ public LocalServerConnection(final LocalServerDataHandler server) {
+ this.server = server;
+
+ final Thread serverRunner1 = new Thread(updateCacheMailbox, "updateCacheMailbox");
+ serverRunner1.setDaemon(true);
+ serverRunner1.start();
+ final Thread serverRunner2 = new Thread(requestChunkMailbox, "requestChunkMailbox");
+ serverRunner2.setDaemon(true);
+ serverRunner2.start();
+ }
+
+ private final ServerRunner updateCacheMailbox = new ServerRunner();
+ private final ServerRunner requestChunkMailbox = new ServerRunner();
+
+ class ServerRunner implements Runnable {
+ private final ConcurrentLinkedQueue<Callable<Void>> mailBox = new ConcurrentLinkedQueue<Callable<Void>>();
+
+ @Override
+ public void run() {
+ while (true) {
+ final Callable<Void> action = mailBox.poll();
+ if (action != null) {
+ try {
+ // System.out.println(Thread.currentThread().getName() + ": " + mailBox.size());
+ action.call();
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ try {
+ Thread.sleep(5);
+ } catch (final InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ public ConcurrentLinkedQueue<Callable<Void>> getMailBox() {
+ return mailBox;
+ }
+ }
+
+ @Override
+ public void getModifier(final ChunkModifier chunkModifier) {
+ this.chunkModifier = chunkModifier;
+ }
+
+ @Override
+ public void update(final int x, final int z) {
+ updateCacheMailbox.getMailBox().offer(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ // Thread.sleep(MathUtils.rand.nextInt(400) + 200);
+ server.updateClientPosition(x, z);
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public void requestChunk(final int x, final int z) {
+ requestChunkMailbox.getMailBox().offer(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ final Chunk chunk = server.getChunk(x, z);
+ // Thread.sleep(MathUtils.rand.nextInt(400) + 200);
+ // Thread.sleep(MathUtils.rand.nextInt(5));
+ chunkModifier.postChunk(x, z, chunk);
+ return null;
+ }
+ });
+ }
+
+ @Override
+ public void setBlock(final int x, final int y, final int z, final int blockId, final BlockSide orientation) {
+ requestChunkMailbox.getMailBox().offer(new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ server.setBlock(x, y, z, blockId);
+ return null;
+ }
+ });
+ }
+
+ public void updatePlayerPosition(final ReadOnlyVector3 location, final ReadOnlyVector3 direction) {
+ }
+
+ public void connect(final String address) {
+ }
+
+ @Override
+ public void close() {
+ server.close();
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/network/LocalServerDataHandler.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/network/LocalServerDataHandler.java
new file mode 100644
index 0000000..34bb4d2
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/network/LocalServerDataHandler.java
@@ -0,0 +1,272 @@
+
+package com.ardor3d.example.craft.network;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import com.ardor3d.math.MathUtils;
+import com.ardorcraft.data.Pos;
+import com.ardorcraft.file.WorldFile;
+import com.ardorcraft.generators.DataGenerator;
+import com.ardorcraft.world.Chunk;
+import com.ardorcraft.world.WorldModifier;
+import com.ardorcraft.world.utils.ChunkDistanceComparator;
+
+/**
+ * Simple data handler that works against a map file.
+ */
+public class LocalServerDataHandler {
+ private static final Logger logger = Logger.getLogger(LocalServerDataHandler.class.getName());
+
+ private final int height;
+ private final int width;
+ private final int gridSize;
+
+ private final WorldFile worldFile;
+ private final DataGenerator generator;
+
+ public LocalServerDataHandler(final int width, final int height, final int gridSize, final DataGenerator generator,
+ final File mapFile) {
+ this.width = width;
+ this.height = height;
+ this.gridSize = gridSize;
+ this.generator = generator;
+
+ try {
+ if (mapFile != null) {
+ worldFile = new WorldFile(mapFile);
+ } else {
+ final File file = new File("world.acr");
+ if (file.exists()) {
+ file.delete();
+ }
+ worldFile = new WorldFile(file);
+ }
+ } catch (final Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void close() {
+ try {
+ worldFile.close();
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ System.out.println("World closed");
+ }
+
+ private byte[] loadChunkData(final int x, final int z) {
+ byte[] block = null;
+ try {
+ if (!worldFile.contains(x, z)) {
+ try {
+ generateChunks(x, z, 1, 1, true);
+ } catch (final Exception e1) {
+ e1.printStackTrace();
+ return null;
+ }
+ }
+
+ block = worldFile.load(x, z);
+ } catch (final Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ return block;
+ }
+
+ private void generateChunks(final int X, final int Z, final int chunksWidth, final int chunksHeight,
+ final boolean onlyNew) throws Exception {
+ final long t = System.currentTimeMillis();
+
+ final byte localBlock[] = new byte[width * width * height];
+ final WorldModifier worldEdit = new LocalBlockModifier(localBlock);
+
+ int nrChunksGenerated = 0;
+ for (int x = 0; x < chunksWidth; x++) {
+ for (int z = 0; z < chunksHeight; z++) {
+ final int xx = X + x;
+ final int zz = Z + z;
+
+ if (onlyNew && worldFile.contains(xx, zz)) {
+ continue;
+ }
+
+ synchronized (generator) {
+ generator.generateChunk(xx * width, zz * width, xx * width + width, zz * width + width, 1, height,
+ worldEdit);
+ Thread.yield();
+
+ worldFile.save(xx, zz, localBlock);
+ Thread.yield();
+ }
+
+ nrChunksGenerated++;
+ }
+ }
+ if (nrChunksGenerated > 0) {
+ logger.info(nrChunksGenerated + " new chunk" + (nrChunksGenerated > 1 ? "s" : "") + " generated. (time="
+ + (System.currentTimeMillis() - t) + ")");
+ }
+ }
+
+ // private final ConcurrentMap<Pos, Chunk> chunkCache = new MapMaker().softValues().makeMap();
+
+ private boolean hasChunk(final int x, final int z) {
+ return true;// chunkCache.containsKey(new Pos(x, 0, z));
+ }
+
+ public synchronized Chunk getChunk(final int x, final int z) {
+ // final Pos chunkPos = new Pos(x, 0, z);
+ // Chunk chunk = chunkCache.get(chunkPos);
+ // if (chunk == null) {
+ final byte[] data = loadChunkData(x, z);
+ final Chunk chunk = new Chunk(data, null);
+ // chunkCache.put(chunkPos, chunk);
+ // }
+ return chunk;
+ }
+
+ public void setBlock(final int x, final int y, final int z, final int blockId) {
+ final int chunkX = MathUtils.floor((float) x / width);
+ final int chunkZ = MathUtils.floor((float) z / width);
+
+ final Chunk chunk = getChunk(chunkX, chunkZ);
+ setChunkBlock(chunk, MathUtils.moduloPositive(x, width), y, MathUtils.moduloPositive(z, width), blockId);
+
+ saveChunk(chunkX, chunkZ, chunk);
+ }
+
+ public int getBlock(final int x, final int y, final int z) {
+ final int chunkX = MathUtils.floor((float) x / width);
+ final int chunkZ = MathUtils.floor((float) z / width);
+
+ final Chunk chunk = getChunk(chunkX, chunkZ);
+ return getChunkBlock(chunk, MathUtils.moduloPositive(x, width), y, MathUtils.moduloPositive(z, width));
+ }
+
+ private void setChunkBlock(final Chunk chunk, final int x, final int y, final int z, final int blockId) {
+ chunk.getBlocks()[x + (y + z * height) * width] = (byte) blockId;
+ }
+
+ private int getChunkBlock(final Chunk chunk, final int x, final int y, final int z) {
+ return chunk.getBlocks()[x + (y + z * height) * width] & 0xff;
+ }
+
+ private void saveChunk(final int x, final int z, final Chunk chunk) {
+ try {
+ final byte localBlock[] = chunk.getBlocks();
+ worldFile.save(x, z, localBlock);
+ } catch (final IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ class LocalBlockModifier implements WorldModifier {
+ private final byte[] localBlock;
+
+ public LocalBlockModifier(final byte[] localBlock) {
+ this.localBlock = localBlock;
+ }
+
+ @Override
+ public void setBlock(final int x, final int y, final int z, final int data) {
+ if (y < 0 || y >= height - 1) {
+ return;
+ }
+ final int xx = MathUtils.moduloPositive(x, width);
+ final int zz = MathUtils.moduloPositive(z, width);
+ localBlock[xx + (y + zz * height) * width] = (byte) data;
+ }
+
+ @Override
+ public int getBlock(final int x, final int y, final int z) {
+ if (y < 0 || y >= height - 1) {
+ return 0;
+ }
+ final int xx = MathUtils.moduloPositive(x, width);
+ final int zz = MathUtils.moduloPositive(z, width);
+ return localBlock[xx + (y + zz * height) * width] & 0xff;
+ }
+ }
+
+ private int oldTileX = Integer.MAX_VALUE;
+ private int oldTileZ = Integer.MAX_VALUE;
+ private final Set<Pos> tileCache = new LinkedHashSet<>();
+
+ class SavingThread implements Runnable {
+ @Override
+ public void run() {
+ // while (!exit) {
+ // try {
+ // final List<Pos> list = savingMailBox.switchAndGet();
+ //
+ // if (!list.isEmpty()) {
+ // for (final Pos pos : list) {
+ // saveChunk(pos.x, pos.z);
+ // Thread.sleep(10);
+ // }
+ // }
+ //
+ // Thread.sleep(1000);
+ // } catch (final InterruptedException e) {
+ // e.printStackTrace();
+ // }
+ // }
+ // exitLatch.countDown();
+ }
+ }
+
+ public void updateClientPosition(int currentTileX, int currentTileZ) {
+ currentTileX /= gridSize / 2;
+ currentTileZ /= gridSize / 2;
+
+ if (currentTileX == oldTileX && currentTileZ == oldTileZ) {
+ return;
+ }
+ oldTileX = currentTileX;
+ oldTileZ = currentTileZ;
+
+ final Set<Pos> newPos = new LinkedHashSet<>();
+ for (int x = 0; x < 3; x++) {
+ for (int z = 0; z < 3; z++) {
+ final int xx = currentTileX + x - 1;
+ final int zz = currentTileZ + z - 1;
+ newPos.add(new Pos(xx, 0, zz));
+ }
+ }
+ final Iterator<Pos> tileIterator = tileCache.iterator();
+ while (tileIterator.hasNext()) {
+ final Pos pos = tileIterator.next();
+
+ if (!newPos.contains(pos)) {
+ tileIterator.remove();
+ } else {
+ newPos.remove(pos);
+ }
+ }
+ if (!newPos.isEmpty()) {
+ try {
+ final List<Pos> sortedPos = new ArrayList<>();
+ sortedPos.addAll(newPos);
+ Collections.sort(sortedPos, new ChunkDistanceComparator(currentTileX, currentTileZ));
+
+ for (final Pos pos : sortedPos) {
+ generateChunks(pos.x * gridSize - gridSize / 2, pos.z * gridSize - gridSize / 2, gridSize, gridSize,
+ true);
+ }
+ } catch (final Exception e) {
+ e.printStackTrace();
+ }
+ }
+ tileCache.addAll(newPos);
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/player/PlayerWithCollision.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/player/PlayerWithCollision.java
new file mode 100644
index 0000000..fdbb427
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/player/PlayerWithCollision.java
@@ -0,0 +1,183 @@
+
+package com.ardor3d.example.craft.player;
+
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.util.ReadOnlyTimer;
+import com.ardorcraft.collision.IntersectionResult;
+import com.ardorcraft.data.Pos;
+import com.ardorcraft.player.PlayerBase;
+import com.ardorcraft.world.BlockWorld;
+
+/**
+ * Player implementation with collision against the world
+ */
+public class PlayerWithCollision extends PlayerBase {
+ protected final Vector3 oldLocation = new Vector3();
+ protected final Vector3 currentLocation = new Vector3();
+ protected final Vector3 moveDir = new Vector3();
+ protected final Vector3 penetrationVec = new Vector3();
+ protected final Vector3 normal = new Vector3();
+ protected final Vector3 mult = new Vector3();
+
+ protected final Vector3 testVec = new Vector3();
+ protected final Vector3 testVecs[];
+
+ protected boolean isOnGround = false;
+
+ public PlayerWithCollision() {
+ testVecs = new Vector3[12];
+ testVecs[0] = new Vector3(-0.4, -1.5, -0.4);
+ testVecs[1] = new Vector3(0.4, -1.5, -0.4);
+ testVecs[2] = new Vector3(0.4, -1.5, 0.4);
+ testVecs[3] = new Vector3(-0.4, -1.5, 0.4);
+ testVecs[4] = new Vector3(-0.4, -0.6, -0.4);
+ testVecs[5] = new Vector3(0.4, -0.6, -0.4);
+ testVecs[6] = new Vector3(0.4, -0.6, 0.4);
+ testVecs[7] = new Vector3(-0.4, -0.6, 0.4);
+ testVecs[8] = new Vector3(-0.4, 0.3, -0.4);
+ testVecs[9] = new Vector3(0.4, 0.3, -0.4);
+ testVecs[10] = new Vector3(0.4, 0.3, 0.4);
+ testVecs[11] = new Vector3(-0.4, 0.3, 0.4);
+ }
+
+ public boolean isPlayerSpace(final Pos pos) {
+ final int X = (int) MathUtils.floor(position.getX());
+ final int Z = (int) MathUtils.floor(position.getZ());
+
+ if (testOnHeight(pos, X, (int) MathUtils.floor(position.getY() + 0.3), Z)) {
+ return true;
+ }
+ if (testOnHeight(pos, X, (int) MathUtils.floor(position.getY() - 0.6), Z)) {
+ return true;
+ }
+ if (testOnHeight(pos, X, (int) MathUtils.floor(position.getY() - 1.4), Z)) {
+ return true;
+ }
+ return false;
+ }
+
+ private boolean testOnHeight(final Pos pos, final int X, final int Y, final int Z) {
+ if (pos.x == X && pos.z == Z && pos.y == Y) {
+ return true;
+ }
+
+ final double fractionX = position.getX() - X - 0.5;
+ final double fractionZ = position.getZ() - Z - 0.5;
+
+ int testX = 0;
+ int testZ = 0;
+ if (fractionX < -0.1) {
+ testX = -1;
+ } else if (fractionX > 0.1) {
+ testX = 1;
+ }
+ if (fractionZ < -0.1) {
+ testZ = -1;
+ } else if (fractionZ > 0.1) {
+ testZ = 1;
+ }
+
+ if (pos.x == X + testX && pos.z == Z && pos.y == Y) {
+ return true;
+ }
+ if (pos.x == X && pos.z == Z + testZ && pos.y == Y) {
+ return true;
+ }
+ if (pos.x == X + testX && pos.z == Z + testZ && pos.y == Y) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public void update(final BlockWorld blockScene, final ReadOnlyTimer timer) {
+ currentLocation.set(position);
+ moveDir.set(currentLocation).subtractLocal(oldLocation);
+ double moveLength = moveDir.length();
+ if (moveLength == 0) {
+ return;
+ }
+ moveDir.normalizeLocal();
+
+ isOnGround = false;
+
+ final IntersectionResult bestIntersection = new IntersectionResult();
+ final IntersectionResult intersectionResult = new IntersectionResult();
+ bestIntersection.length = Double.MAX_VALUE;
+ for (int i = 0; i < testVecs.length; i++) {
+ testVec.set(oldLocation).addLocal(testVecs[i]);
+ blockScene.traceCollision(testVec, moveDir, 10, intersectionResult);
+ if (intersectionResult.hit && intersectionResult.length < moveLength) {
+ if (intersectionResult.length < bestIntersection.length) {
+ bestIntersection.set(intersectionResult);
+ }
+ }
+ }
+
+ if (bestIntersection.length < moveLength) {
+ mult.set(moveDir).multiplyLocal(bestIntersection.length).addLocal(oldLocation);
+
+ for (int j = 0; j < 3; j++) {
+ final double penetration = moveLength - bestIntersection.length;
+ penetrationVec.set(moveDir).multiplyLocal(penetration);
+
+ final int yDir = bestIntersection.oldPos.y - bestIntersection.pos.y;
+ if (yDir != 0) {
+ velocity.setY(0);
+ if (yDir > 0) {
+ isOnGround = true;
+ }
+ }
+ final int xDir = bestIntersection.oldPos.x - bestIntersection.pos.x;
+ if (xDir != 0) {
+ velocity.setX(0);
+ }
+ final int zDir = bestIntersection.oldPos.z - bestIntersection.pos.z;
+ if (zDir != 0) {
+ velocity.setZ(0);
+ }
+
+ normal.set(bestIntersection.oldPos.x - bestIntersection.pos.x,
+ bestIntersection.oldPos.y - bestIntersection.pos.y,
+ bestIntersection.oldPos.z - bestIntersection.pos.z);
+ final double dot = penetrationVec.dot(normal);
+ normal.multiplyLocal(dot);
+ penetrationVec.subtractLocal(normal);
+
+ moveDir.set(penetrationVec).normalizeLocal();
+ bestIntersection.length = penetrationVec.length();
+ moveLength = bestIntersection.length;
+ for (int i = 0; i < testVecs.length; i++) {
+ testVec.set(mult).addLocal(testVecs[i]);
+ blockScene.traceCollision(testVec, moveDir, 10, intersectionResult);
+ if (intersectionResult.hit && intersectionResult.length < moveLength) {
+ if (intersectionResult.length < bestIntersection.length) {
+ bestIntersection.set(intersectionResult);
+ }
+ }
+ }
+ if (!bestIntersection.hit) {
+ break;
+ }
+
+ moveDir.multiplyLocal(bestIntersection.length);
+ mult.addLocal(moveDir);
+ moveDir.set(penetrationVec).normalizeLocal();
+ }
+
+ currentLocation.set(mult);
+ }
+
+ if (currentLocation.getY() < 0.5) {
+ currentLocation.setY(0.5);
+ }
+
+ position.set(currentLocation);
+ oldLocation.set(position);
+ }
+
+ public boolean isOnGround() {
+ return isOnGround;
+ }
+}
diff --git a/ardor3d-examples/src/main/java/com/ardor3d/example/craft/player/PlayerWithPhysics.java b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/player/PlayerWithPhysics.java
new file mode 100644
index 0000000..3dbecb4
--- /dev/null
+++ b/ardor3d-examples/src/main/java/com/ardor3d/example/craft/player/PlayerWithPhysics.java
@@ -0,0 +1,127 @@
+
+package com.ardor3d.example.craft.player;
+
+import com.ardor3d.input.logical.LogicalLayer;
+import com.ardor3d.math.MathUtils;
+import com.ardor3d.math.Vector3;
+import com.ardor3d.util.ReadOnlyTimer;
+import com.ardorcraft.control.FlyControl;
+import com.ardorcraft.control.WalkControl;
+import com.ardorcraft.world.BlockWorld;
+
+/**
+ * A player implementation with walk/fly modes and gravity and jumping.
+ */
+public class PlayerWithPhysics extends PlayerWithCollision {
+ private boolean jump = false;
+ private double time;
+ private final double stepTime = 1.0 / 100.0;
+ private boolean walking = false;
+ private final Vector3 damping = new Vector3(0.85, 1.0, 0.85);
+ private final double gravity = -0.3;
+ private final double jumpForce = 9;
+ private final double speed = 0.9;
+
+ private final WalkControl walkControl;
+ private final FlyControl flyControl;
+
+ public PlayerWithPhysics(final LogicalLayer logicalLayer) {
+ flyControl = FlyControl.setupTriggers(this, logicalLayer, Vector3.UNIT_Y, false);
+ // flyControl.setMoveSpeed(50);
+ flyControl.disable();
+ walkControl = WalkControl.setupTriggers(this, logicalLayer, Vector3.UNIT_Y, false);
+ }
+
+ @Override
+ public void update(final BlockWorld blockScene, final ReadOnlyTimer timer) {
+ final double tpf = timer.getTimePerFrame();
+
+ time += tpf;
+
+ keepAboveGround(blockScene);
+
+ int ticks = 0;
+ while (time > stepTime) {
+ final double step = stepTime;
+
+ getVelocity().addLocal(getAcceleration().multiply(speed, null));
+ if (walking) {
+ getVelocity().addLocal(0, gravity, 0);
+ if (jump && isOnGround()) {
+ getVelocity().addLocal(0, jumpForce, 0);
+ }
+ }
+ getPosition().addLocal(getVelocity().multiply(step, null));
+
+ getVelocity().multiplyLocal(damping);
+
+ // Update collision etc
+ super.update(blockScene, timer);
+
+ time -= stepTime;
+ ticks++;
+ jump = false;
+ }
+ if (ticks > 0) {
+ getAcceleration().set(0, 0, 0);
+ }
+ jump = false;
+ }
+
+ private void keepAboveGround(final BlockWorld blockScene) {
+ final int X = (int) MathUtils.floor(position.getX());
+ final int Y = (int) MathUtils.floor(position.getY());
+ final int Z = (int) MathUtils.floor(position.getZ());
+
+ int block = blockScene.getBlock(X, Y, Z);
+ if (block != 0 && blockScene.getBlockUtil().getIsCollidable(block)) {
+ for (int y = Y; y < blockScene.getHeight(); y++) {
+ block = blockScene.getBlock(X, y, Z);
+ if (block == 0 || !blockScene.getBlockUtil().getIsCollidable(block)) {
+ position.setY(y + 2);
+ break;
+ }
+ }
+ }
+ }
+
+ double t = 0;
+
+ public void jump() {
+ if (!jump && isOnGround()) {
+ jump = true;
+ }
+ }
+
+ public boolean isWalking() {
+ return walking;
+ }
+
+ public void setWalking(final boolean walking) {
+ this.walking = walking;
+ if (isWalking()) {
+ walkControl.enable();
+ flyControl.disable();
+ } else {
+ walkControl.disable();
+ flyControl.enable();
+ }
+ }
+
+ public WalkControl getWalkControl() {
+ return walkControl;
+ }
+
+ public FlyControl getFlyControl() {
+ return flyControl;
+ }
+
+ public void disableControls() {
+ walkControl.disable();
+ flyControl.disable();
+ }
+
+ public void enableControls() {
+ setWalking(walking);
+ }
+}