diff options
Diffstat (limited to 'ardor3d-examples')
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> + * <CODE> + * FREQ=60 + * WIDTH=1280 + * HEIGHT=1024 + * DEPTH=32 + * FULLSCREEN=false + * </CODE> + * </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); + } +} |