diff options
77 files changed, 10556 insertions, 1 deletions
diff --git a/ardor3d-craft/LICENSE b/ardor3d-craft/LICENSE new file mode 100644 index 0000000..fcc7425 --- /dev/null +++ b/ardor3d-craft/LICENSE @@ -0,0 +1,24 @@ +Ardor3D is licensed as follows: + +/** + * Copyright (c) 2008-2014 Ardor Labs, Inc. + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + */
\ No newline at end of file diff --git a/ardor3d-craft/NiceDataGenerator_Map.acr b/ardor3d-craft/NiceDataGenerator_Map.acr Binary files differnew file mode 100644 index 0000000..a76d88b --- /dev/null +++ b/ardor3d-craft/NiceDataGenerator_Map.acr diff --git a/ardor3d-craft/README.md b/ardor3d-craft/README.md new file mode 100644 index 0000000..eade830 --- /dev/null +++ b/ardor3d-craft/README.md @@ -0,0 +1,25 @@ +# ArdorCraft API + +## Try it +[Try running one of the examples!](https://jogamp.org/cgit/ardor3d.git/tree/ardor3d-examples/src/main/java/com/ardor3d/example/craft) + +## Learn it +[Features](http://tinyurl.com/mpuu8or) + +[Documentation](http://jogamp.org/deployment/ardor3d/javadoc/) + + +Built with the best open-source 3D engine [Ardor3D](https://jogamp.org/cgit/ardor3d.git) + +## Screenshots from some of the examples you can build on: + +**http://www.youtube.com/watch?v=FZKYiNBafUc** + +![Screenshot](http://i.imgur.com/qf5Z3.jpg) +![Screenshot](http://i.imgur.com/9RpZF.png) +![Screenshot](http://i.imgur.com/lv30u.jpg) +![Screenshot](http://i.imgur.com/PxlNx.png) +![Screenshot](http://i.imgur.com/nEEjS.png) +![Screenshot](http://i.imgur.com/WfVXB.jpg) +![Screenshot](http://i.imgur.com/3Nwac.png) +![Screenshot](http://i.imgur.com/cTuYb.png) diff --git a/ardor3d-craft/build.gradle b/ardor3d-craft/build.gradle new file mode 100644 index 0000000..0ef8d8c --- /dev/null +++ b/ardor3d-craft/build.gradle @@ -0,0 +1,10 @@ + +description = 'Ardor 3D Craft' +dependencies { + compile project(':ardor3d-awt') + compile project(':ardor3d-core') + compile group: 'com.sun.xml.bind', name: 'jaxb-core', version:'2.3.0.1' + compile group: 'javax.xml.bind', name: 'jaxb-api', version:'2.3.1' + compile group: 'com.sun.xml.bind', name: 'jaxb-impl', version:'2.3.1' +} + diff --git a/ardor3d-craft/pom.xml b/ardor3d-craft/pom.xml new file mode 100644 index 0000000..9118608 --- /dev/null +++ b/ardor3d-craft/pom.xml @@ -0,0 +1,42 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.jogamp.ardor3d</groupId> + <artifactId>ardor3d</artifactId> + <version>1.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>ardor3d-craft</artifactId> + <packaging>bundle</packaging> + <name>Ardor 3D Craft</name> + + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>ardor3d-core</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>ardor3d-awt</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.sun.xml.bind</groupId> + <artifactId>jaxb-core</artifactId> + <version>2.3.0.1</version> + </dependency> + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>jaxb-api</artifactId> + <version>2.3.1</version> + </dependency> + <dependency> + <groupId>com.sun.xml.bind</groupId> + <artifactId>jaxb-impl</artifactId> + <version>2.3.1</version> + </dependency> + </dependencies> + +</project>
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/collision/HitTester.java b/ardor3d-craft/src/main/java/com/ardorcraft/collision/HitTester.java new file mode 100644 index 0000000..c0c642f --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/collision/HitTester.java @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.collision; + +/** + * A HitTester allows for user configurable picking. + */ +public interface HitTester { + boolean isHit(int blockId); +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/collision/IntersectionResult.java b/ardor3d-craft/src/main/java/com/ardorcraft/collision/IntersectionResult.java new file mode 100644 index 0000000..b13da86 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/collision/IntersectionResult.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.collision; + +import com.ardorcraft.data.Pos; + +/** + * The result of a picking/tracing operation. + * <ul> + * <li>hit = was there a hit during this intersection test + * <li>oldPos = the last block grid position before a hit (which is the position in front of the hit block face) + * <li>pos = the block grid position that was hit + * <li>length = the length of the ray from the source to the hit position + * </ul> + */ +public class IntersectionResult { + public boolean hit; + public Pos oldPos = new Pos(); + public Pos pos = new Pos(); + public double length; + + public void set(final IntersectionResult result) { + hit = result.hit; + oldPos = new Pos(result.oldPos); + pos = new Pos(result.pos); + length = result.length; + } + + @Override + public String toString() { + return "IntersectionResult [hit=" + hit + ", length=" + length + ", oldPos=" + oldPos + ", pos=" + pos + "]"; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/collision/Tracer.java b/ardor3d-craft/src/main/java/com/ardorcraft/collision/Tracer.java new file mode 100644 index 0000000..9dad657 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/collision/Tracer.java @@ -0,0 +1,217 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.collision; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardorcraft.world.BlockProvider; + +/** + * Ray tracing for a block world. + */ +public class Tracer { + private double mult = 1; + private final Vector3 tmax = new Vector3(); + private final Vector3 tdelta = new Vector3(); + private final Vector3 newPos = new Vector3(); + + private static final class DefaultHitTester implements HitTester { + @Override + public boolean isHit(final int blockId) { + return blockId != 0; + } + } + + private final int maxHeight; + private final HitTester hitTester; + private final BlockProvider provider; + + public Tracer(final BlockProvider provider) { + this.provider = provider; + hitTester = new DefaultHitTester(); + maxHeight = -1; + } + + public Tracer(final BlockProvider provider, final HitTester hitTester) { + this.provider = provider; + this.hitTester = hitTester; + maxHeight = -1; + } + + public Tracer(final BlockProvider provider, final HitTester hitTester, final int maxHeight) { + this.provider = provider; + this.hitTester = hitTester; + this.maxHeight = maxHeight; + } + + public void traceCollision(ReadOnlyVector3 curpos, ReadOnlyVector3 raydir, final int iterations, + final IntersectionResult result) { + tmax.set(0, 0, 0); + tdelta.set(0, 0, 0); + + mult = 1; + boolean back = false; + + result.hit = false; + result.length = 0; + + if (curpos.getY() < 0) { + return; + } + if (maxHeight > 0 && curpos.getY() >= maxHeight - 1 && raydir.getY() >= 0) { + return; + } + + if (maxHeight > 0 && curpos.getY() >= maxHeight - 1) { + final double diff = maxHeight - 1 - curpos.getY(); + final double t = diff / raydir.getY(); + newPos.set(raydir).multiplyLocal(t).addLocal(curpos); + curpos = newPos; + } + + int X = (int) MathUtils.floor(curpos.getX()); + int Y = (int) MathUtils.floor(curpos.getY()); + int Z = (int) MathUtils.floor(curpos.getZ()); + + final int block1 = provider.getBlock(X, Y, Z); + if (block1 != 0 && hitTester.isHit(block1)) { + raydir = new Vector3(raydir).negateLocal(); + mult = -1; + back = true; + } + + int stepX, stepY, stepZ; + double cbx, cby, cbz; + + if (raydir.getX() > 0.0) { + stepX = 1; + cbx = X + 1; + } else { + stepX = -1; + cbx = X; + } + if (raydir.getY() > 0.0) { + stepY = 1; + cby = Y + 1; + } else { + stepY = -1; + cby = Y; + } + if (raydir.getZ() > 0.0) { + stepZ = 1; + cbz = Z + 1; + } else { + stepZ = -1; + cbz = Z; + } + + if (raydir.getX() != 0) { + final double rxr = 1.0 / raydir.getX(); + tmax.setX((cbx - curpos.getX()) * rxr); + tdelta.setX(stepX * rxr); + } else { + tmax.setX(1000000); + } + if (raydir.getY() != 0) { + final double ryr = 1.0 / raydir.getY(); + tmax.setY((cby - curpos.getY()) * ryr); + tdelta.setY(stepY * ryr); + } else { + tmax.setY(1000000); + } + if (raydir.getZ() != 0) { + final double rzr = 1.0 / raydir.getZ(); + tmax.setZ((cbz - curpos.getZ()) * rzr); + tdelta.setZ(stepZ * rzr); + } else { + tmax.setZ(1000000); + } + + int oldX = X, oldY = Y, oldZ = Z; + + for (int i = 0; i < iterations; i++) { + if (tmax.getX() < tmax.getY()) { + if (tmax.getX() < tmax.getZ()) { + X = X + stepX; + final int block = provider.getBlock(X, Y, Z); + final boolean isHit = block != 0 && hitTester.isHit(block); + if (back && !isHit || !back && isHit) { + gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ); + result.hit = true; + return; + } + tmax.setX(tmax.getX() + tdelta.getX()); + } else { + Z = Z + stepZ; + final int block = provider.getBlock(X, Y, Z); + final boolean isHit = block != 0 && hitTester.isHit(block); + if (back && !isHit || !back && isHit) { + gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ); + result.hit = true; + return; + } + tmax.setZ(tmax.getZ() + tdelta.getZ()); + } + } else { + if (tmax.getY() < tmax.getZ()) { + Y = Y + stepY; + if (maxHeight > 0 && Y >= maxHeight - 1) { + return; + } + final int block = provider.getBlock(X, Y, Z); + final boolean isHit = block != 0 && hitTester.isHit(block); + if (back && !isHit || !back && isHit) { + gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ); + result.hit = true; + return; + } + tmax.setY(tmax.getY() + tdelta.getY()); + } else { + Z = Z + stepZ; + final int block = provider.getBlock(X, Y, Z); + final boolean isHit = block != 0 && hitTester.isHit(block); + if (back && !isHit || !back && isHit) { + gatherMin(result, tmax, X, Y, Z, oldX, oldY, oldZ); + result.hit = true; + return; + } + tmax.setZ(tmax.getZ() + tdelta.getZ()); + } + } + + oldX = X; + oldY = Y; + oldZ = Z; + } + } + + private void gatherMin(final IntersectionResult result, final Vector3 tmax, final int X, final int Y, final int Z, + final int oldX, final int oldY, final int oldZ) { + result.oldPos.set(oldX, oldY, oldZ); + result.pos.set(X, Y, Z); + + double min = tmax.getX(); + if (tmax.getY() < min) { + min = tmax.getY(); + } + if (tmax.getZ() < min) { + min = tmax.getZ(); + } + + final double epsilon = 0.0001; + + double length = min * mult; + if (length > 0) { + length = Math.max(length - epsilon, 0.0); + } else if (length < 0) { + length = Math.min(length - epsilon, 0.0); + } + + result.length = length; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/ButtonSet.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/ButtonSet.java new file mode 100644 index 0000000..4197e18 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/ButtonSet.java @@ -0,0 +1,82 @@ +/** + * + */ +package com.ardorcraft.control; + +import com.ardor3d.input.Key; +import com.ardor3d.input.MouseButton; + +import java.util.Collection; +import java.util.TreeSet; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; + +/** + * @author Nathan + * + * A set of keys or mouse buttons to map to a function + * for creating configurable controls. + */ +public class ButtonSet +{ + /** + * The set of keys in the set. + */ + @XmlElementWrapper(name="Keys") + @XmlElement(name="Key") + public TreeSet<Key> keys; + + /** + * The set of mouse buttons in the set. + */ + @XmlElementWrapper(name="MouseButtons") + @XmlElement(name="Button") + public TreeSet<MouseButton> mouseButtons; + + /** + * Default constructor just creates + * an empty set. + */ + public ButtonSet() + { + keys = new TreeSet<>(); + mouseButtons = new TreeSet<>(); + } + + /** + * Create a union of a number of + * button sets. + * @param buttonSets + * The buttonsets to combine + */ + public ButtonSet(ButtonSet...buttonSets) + { + keys = new TreeSet<>(); + mouseButtons = new TreeSet<>(); + + for(ButtonSet set : buttonSets) + { + keys.addAll(set.keys); + mouseButtons.addAll(set.mouseButtons); + } + } + + /** + * Constructs a set containing the + * union of the sets passed in. + * @param buttonSets + * The sets to combine. + */ + public ButtonSet(Collection<ButtonSet> buttonSets) + { + keys = new TreeSet<>(); + mouseButtons = new TreeSet<>(); + + for(ButtonSet set : buttonSets) + { + keys.addAll(set.keys); + mouseButtons.addAll(set.mouseButtons); + } + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/FlyControl.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/FlyControl.java new file mode 100644 index 0000000..237df9f --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/FlyControl.java @@ -0,0 +1,315 @@ +/** + * 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.ardorcraft.control; + +import java.util.function.Predicate; + +import com.ardor3d.framework.Canvas; +//import com.ardor3d.input.Key; +//import com.ardor3d.input.KeyboardState; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.InputTrigger; +import com.ardor3d.input.logical.LogicalLayer; +import com.ardor3d.input.logical.TriggerAction; +import com.ardor3d.input.logical.TriggerConditions; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardorcraft.player.PlayerBase; + +public class FlyControl { + + private final Vector3 _upAxis = new Vector3(); + private double _mouseRotateSpeed = .005; + private double _moveSpeed = 15; + private double _keyRotateSpeed = 2.25; + private final Matrix3 _workerMatrix = new Matrix3(); + private final Vector3 _workerStoreA = new Vector3(); + private InputTrigger _mouseTrigger; + private InputTrigger _keyTrigger; + private final LogicalLayer layer; + private final PlayerBase player; + + private SomeButtonsDownPredicate moveLeftPredicate; + private SomeButtonsDownPredicate moveRightPredicate; + private SomeButtonsDownPredicate moveForwardPredicate; + private SomeButtonsDownPredicate moveBackPredicate; + private SomeButtonsDownPredicate turnLeftPredicate; + private SomeButtonsDownPredicate turnRightPredicate; + private SomeButtonsDownPredicate turnUpPredicate; + private SomeButtonsDownPredicate turnDownPredicate; + private UprightFPSMoveConfig moveConfigMember; + + public FlyControl( + final PlayerBase player, + final LogicalLayer layer, + final ReadOnlyVector3 upAxis, + UprightFPSMoveConfig moveConfigParam) { + _upAxis.set(upAxis); + this.layer = layer; + this.player = player; + moveConfigMember = moveConfigParam; + moveLeftPredicate = + new SomeButtonsDownPredicate(moveConfigMember.MoveLeft); + moveRightPredicate = + new SomeButtonsDownPredicate(moveConfigMember.MoveRight); + moveForwardPredicate = + new SomeButtonsDownPredicate(moveConfigMember.MoveForward); + moveBackPredicate = + new SomeButtonsDownPredicate(moveConfigMember.MoveBack); + turnLeftPredicate = + new SomeButtonsDownPredicate(moveConfigMember.TurnLeft); + turnRightPredicate = + new SomeButtonsDownPredicate(moveConfigMember.TurnRight); + turnUpPredicate = + new SomeButtonsDownPredicate(moveConfigMember.TurnUp); + turnDownPredicate = + new SomeButtonsDownPredicate(moveConfigMember.TurnDown); + } + + public ReadOnlyVector3 getUpAxis() { + return _upAxis; + } + + public void setUpAxis(final ReadOnlyVector3 upAxis) { + _upAxis.set(upAxis); + } + + public double getMouseRotateSpeed() { + return _mouseRotateSpeed; + } + + public void setMouseRotateSpeed(final double speed) { + _mouseRotateSpeed = speed; + } + + public double getMoveSpeed() { + return _moveSpeed; + } + + public void setMoveSpeed(final double speed) { + _moveSpeed = speed; + } + + public double getKeyRotateSpeed() { + return _keyRotateSpeed; + } + + public void setKeyRotateSpeed(final double speed) { + _keyRotateSpeed = speed; + } + + protected void move( + final TwoInputStates state, + final double tpf) + { + // MOVEMENT + int moveFB = 0, strafeLR = 0; + if (moveForwardPredicate.test(state)) { + moveFB += 1; + } + if (moveBackPredicate.test(state)) { + moveFB -= 1; + } + if (moveLeftPredicate.test(state)) { + strafeLR += 1; + } + if (moveRightPredicate.test(state)) { + strafeLR -= 1; + } + + if (moveFB != 0 || strafeLR != 0) { + final Vector3 loc = _workerStoreA.zero(); + if (moveFB == 1) { + loc.addLocal(player.getDirection()); + } else if (moveFB == -1) { + loc.subtractLocal(player.getDirection()); + } + if (strafeLR == 1) { + loc.addLocal(player.getLeft()); + } else if (strafeLR == -1) { + loc.subtractLocal(player.getLeft()); + } + loc.normalizeLocal().multiplyLocal(_moveSpeed * tpf) + .addLocal(player.getPosition()); + player.getPosition().set(loc); + } + + // ROTATION + int rotX = 0, rotY = 0; + if (turnUpPredicate.test(state)) { + rotY -= 1; + } + if (turnDownPredicate.test(state)) { + rotY += 1; + } + if (turnLeftPredicate.test(state)) { + rotX += 1; + } + if (turnRightPredicate.test(state)) { + rotX -= 1; + } + if (rotX != 0 || rotY != 0) { + rotate(rotX * _keyRotateSpeed / _mouseRotateSpeed * tpf, rotY + * _keyRotateSpeed / _mouseRotateSpeed * tpf); + } + } + + protected void rotate(final double dx, final double dy) { + + if (dx != 0) { + _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dx, + _upAxis != null ? _upAxis : player.getUp()); + _workerMatrix.applyPost(player.getLeft(), _workerStoreA); + player.getLeft().set(_workerStoreA); + _workerMatrix.applyPost(player.getDirection(), _workerStoreA); + player.getDirection().set(_workerStoreA); + _workerMatrix.applyPost(player.getUp(), _workerStoreA); + player.getUp().set(_workerStoreA); + } + + if (dy != 0) { + _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dy, + player.getLeft()); + _workerMatrix.applyPost(player.getLeft(), _workerStoreA); + player.getLeft().set(_workerStoreA); + _workerMatrix.applyPost(player.getDirection(), _workerStoreA); + player.getDirection().set(_workerStoreA); + _workerMatrix.applyPost(player.getUp(), _workerStoreA); + player.getUp().set(_workerStoreA); + } + + player.normalize(); + } + + /** + * @param layer + * the logical layer to register with + * @param upAxis + * the up axis of the camera + * @param dragOnly + * if true, mouse input will only rotate the camera if one of the + * mouse buttons (left, center or right) is down. + * @return a new FlyControl object + */ + public static FlyControl setupTriggers(final PlayerBase player, + final LogicalLayer layer, final ReadOnlyVector3 upAxis, + final boolean dragOnly) { + + final FlyControl control = + new FlyControl( + player, + layer, + upAxis, + new UprightFPSMoveConfig(UprightFPSMoveConfig.defaultControls.RightHanded)); + control.setupKeyboardTriggers(layer); + control.setupMouseTriggers(layer, dragOnly); + return control; + } + + /** + * Deregister the triggers of the given FlyControl from the given + * LogicalLayer. + * + * @param layer + * @param control + */ + public static void removeTriggers(final LogicalLayer layer, + final FlyControl control) { + if (control._mouseTrigger != null) { + layer.deregisterTrigger(control._mouseTrigger); + } + if (control._keyTrigger != null) { + layer.deregisterTrigger(control._keyTrigger); + } + } + + public void setupMouseTriggers(final LogicalLayer layer, + final boolean dragOnly) { + final FlyControl control = this; + // Mouse look + final Predicate<TwoInputStates> someMouseDown = + TriggerConditions.leftButtonDown().or(TriggerConditions.rightButtonDown()).or(TriggerConditions.middleButtonDown()); + final Predicate<TwoInputStates> dragged = TriggerConditions.mouseMoved().and(someMouseDown); + final TriggerAction dragAction = new TriggerAction() { + + // Test boolean to allow us to ignore first mouse event. First event + // can wildly vary based on platform. + private boolean firstPing = true; + + @Override + public void perform(final Canvas source, + final TwoInputStates inputStates, final double tpf) { + final MouseState mouse = inputStates.getCurrent() + .getMouseState(); + if (mouse.getDx() != 0 || mouse.getDy() != 0) { + if (!firstPing) { + control.rotate(-mouse.getDx(), -mouse.getDy()); + } else { + firstPing = false; + } + } + } + }; + + _mouseTrigger = new InputTrigger(dragOnly ? dragged + : TriggerConditions.mouseMoved(), dragAction); + layer.registerTrigger(_mouseTrigger); + } + + public Predicate<TwoInputStates> setupKeyboardTriggers( + final LogicalLayer layer) { + + final FlyControl control = this; + + // WASD control + final Predicate<TwoInputStates> keysHeld = + moveConfigMember.createSinglePredicate(moveConfigMember.createKeysDownPredicates()); + + final TriggerAction moveAction = new TriggerAction() { + @Override + public void perform(final Canvas source, + final TwoInputStates inputStates, final double tpf) { + control.move(inputStates, tpf); + } + }; + _keyTrigger = new InputTrigger(keysHeld, moveAction); + layer.registerTrigger(_keyTrigger); + return keysHeld; + } + + public InputTrigger getKeyTrigger() { + return _keyTrigger; + } + + public InputTrigger getMouseTrigger() { + return _mouseTrigger; + } + + public void enable() { + if (!layer.getTriggers().contains(_keyTrigger)) { + layer.registerTrigger(_keyTrigger); + } + if (!layer.getTriggers().contains(_mouseTrigger)) { + layer.registerTrigger(_mouseTrigger); + } + } + + public void disable() { + if (layer.getTriggers().contains(_keyTrigger)) { + layer.deregisterTrigger(_keyTrigger); + } + if (layer.getTriggers().contains(_mouseTrigger)) { + layer.deregisterTrigger(_mouseTrigger); + } + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/ITriggerGroup.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/ITriggerGroup.java new file mode 100644 index 0000000..3669669 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/ITriggerGroup.java @@ -0,0 +1,33 @@ +/** + * + */ +package com.ardorcraft.control; + +import com.ardor3d.input.logical.LogicalLayer; + +/** + * @author Nathan + * + */ +public interface ITriggerGroup +{ + + /** + * Add the triggers to the specified + * logical layer. + * @param layer + * The logical layer to add + * the triggers to. + */ + void AddToLayer(LogicalLayer layer); + + /** + * Removes the trigger from the + * specified logival layer + * @param layer + * The logical layer to + * remove the triggers from. + */ + void RemoveFromLayer(LogicalLayer layer); + +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/SomeButtonsDownPredicate.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/SomeButtonsDownPredicate.java new file mode 100644 index 0000000..be7003f --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/SomeButtonsDownPredicate.java @@ -0,0 +1,53 @@ +/** + * + */ +package com.ardorcraft.control; + +import com.ardor3d.input.InputState; +import com.ardor3d.input.Key; +import com.ardor3d.input.MouseButton; + +import java.util.function.Predicate; + +import com.ardor3d.input.ButtonState; +import com.ardor3d.input.logical.TwoInputStates; + +/** + * @author nathan + * + */ +public class SomeButtonsDownPredicate implements + Predicate<TwoInputStates> +{ + private ButtonSet buttons; + + public SomeButtonsDownPredicate(ButtonSet buttonsParam) + { + buttons = buttonsParam; + } + + @Override + public boolean test(TwoInputStates states) + { + InputState current = states.getCurrent(); + + for(Key currentKey : buttons.keys) + { + if(current.getKeyboardState().isDown(currentKey)) + { + return true; + } + } + + for(MouseButton currentButton : buttons.mouseButtons) + { + if(current.getMouseState().getButtonState(currentButton) == ButtonState.DOWN) + { + return true; + } + } + + return false; + } + +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFPSMoveConfig.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFPSMoveConfig.java new file mode 100644 index 0000000..d1186d2 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFPSMoveConfig.java @@ -0,0 +1,118 @@ +package com.ardorcraft.control; + +import com.ardor3d.input.Key; +import java.util.ArrayList; +import java.util.function.Predicate; + +import javax.xml.bind.annotation.XmlRootElement; + +import com.ardor3d.input.logical.TriggerConditions; +import com.ardor3d.input.logical.TwoInputStates; + +@XmlRootElement(name="Controls") +public class UprightFPSMoveConfig +{ + public enum defaultControls + { + LeftHanded, + RightHanded + } + + public ButtonSet MoveLeft = new ButtonSet(); + public ButtonSet MoveRight = new ButtonSet(); + public ButtonSet MoveForward = new ButtonSet(); + public ButtonSet MoveBack = new ButtonSet(); + public ButtonSet TurnLeft = new ButtonSet(); + public ButtonSet TurnRight = new ButtonSet(); + public ButtonSet TurnUp = new ButtonSet(); + public ButtonSet TurnDown = new ButtonSet(); + + public UprightFPSMoveConfig() + { + + } + + public UprightFPSMoveConfig(defaultControls config) + { + TurnLeft.keys.add(Key.LEFT); + TurnRight.keys.add(Key.RIGHT); + TurnUp.keys.add(Key.UP); + TurnDown.keys.add(Key.DOWN); + + if(config == defaultControls.LeftHanded) + { + MoveLeft.keys.add(Key.J); + MoveRight.keys.add(Key.L); + MoveForward.keys.add(Key.I); + MoveBack.keys.add(Key.K); + } + else + { + MoveForward.keys.add(Key.W); + MoveLeft.keys.add(Key.A); + MoveBack.keys.add(Key.S); + MoveRight.keys.add(Key.D); + } + } + + ArrayList<Predicate<TwoInputStates> > + createSomeMoveKeysDownPredicates() + { + ArrayList<Predicate<TwoInputStates> > + tempPredicates = new ArrayList<>(); + + tempPredicates.add( + new SomeButtonsDownPredicate(MoveForward)); + tempPredicates.add( + new SomeButtonsDownPredicate(MoveLeft)); + tempPredicates.add( + new SomeButtonsDownPredicate(MoveBack)); + tempPredicates.add( + new SomeButtonsDownPredicate(MoveRight)); + + return tempPredicates; + } + + ArrayList<Predicate<TwoInputStates> > + createSomeTurnKeyPredicates() + { + ArrayList<Predicate<TwoInputStates> > + tempPredicates = + new ArrayList<>(); + + tempPredicates.add( + new SomeButtonsDownPredicate(TurnUp)); + tempPredicates.add( + new SomeButtonsDownPredicate(TurnLeft)); + tempPredicates.add( + new SomeButtonsDownPredicate(TurnDown)); + tempPredicates.add( + new SomeButtonsDownPredicate(TurnRight)); + + return tempPredicates; + } + + ArrayList<Predicate<TwoInputStates>> + createKeysDownPredicates() + { + ArrayList<Predicate<TwoInputStates> > moveKeys = + createSomeMoveKeysDownPredicates(); + ArrayList<Predicate<TwoInputStates> > turnKeys = + createSomeTurnKeyPredicates(); + + ArrayList<Predicate<TwoInputStates> > result = + new ArrayList<>(); + result.addAll(moveKeys); + result.addAll(turnKeys); + + return result; + } + + Predicate<TwoInputStates> createSinglePredicate(final Iterable<Predicate<TwoInputStates>> predicates) { + Predicate<TwoInputStates> result = TriggerConditions.alwaysFalse(); + for (final Predicate<TwoInputStates> predicate : predicates) { + result = result.or(predicate); + } + return result; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFlyControlTriggers.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFlyControlTriggers.java new file mode 100644 index 0000000..8aa05a4 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/UprightFlyControlTriggers.java @@ -0,0 +1,340 @@ +package com.ardorcraft.control; + +import java.util.function.Predicate; + +import com.ardor3d.framework.Canvas; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.InputTrigger; +import com.ardor3d.input.logical.LogicalLayer; +import com.ardor3d.input.logical.TriggerAction; +import com.ardor3d.input.logical.TriggerConditions; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardorcraft.player.PlayerBase; + +/** + * + * Triggers for flying that enforce + * an upright position. + * @author nathan + * + */ +public class UprightFlyControlTriggers implements ITriggerGroup +{ + + private final Vector3 upAxis = new Vector3(); + private double mouseRotateSpeed = .005; + private double moveSpeed = 15; + private double keyRotateSpeed = 2.25; + private final Matrix3 workerMatrix = new Matrix3(); + private final Vector3 workerStoreA = new Vector3(); + private final InputTrigger moveTrigger; + private final InputTrigger turnTrigger; + private final PlayerBase player; + + // The first mouse state is pretty random, + // so arrange to ignore it. I'm not sure + // this should be there, but I'm not sure + // if Ardor3D allows not firing the + // triggers on the first update. + private boolean firstPing = true; + + private Predicate<TwoInputStates> mouseTurnPredicate; + + private SomeButtonsDownPredicate moveLeftPredicate; + private SomeButtonsDownPredicate moveRightPredicate; + private SomeButtonsDownPredicate moveForwardPredicate; + private SomeButtonsDownPredicate moveBackPredicate; + private SomeButtonsDownPredicate turnLeftPredicate; + private SomeButtonsDownPredicate turnRightPredicate; + private SomeButtonsDownPredicate turnUpPredicate; + private SomeButtonsDownPredicate turnDownPredicate; + private UprightFPSMoveConfig moveConfig; + + /** + * Creates a trigger set for flying + * in an upright first person shooter. + * @param playerParam + * The player object to update + * @param upAxisParam + * The up axis for the player + * @param mouseLookDragOnly + * Whether the player needs to + * press a mouse button to + * turn. + * @param moveConfigParam + * The keys and possibly mouse + * buttons to manage movement. + */ + public UprightFlyControlTriggers( + final PlayerBase playerParam, + final ReadOnlyVector3 upAxisParam, + final boolean mouseLookDragOnly, + final UprightFPSMoveConfig moveConfigParam) + { + player = playerParam; + upAxis.set(upAxisParam); + moveConfig = moveConfigParam; + + moveLeftPredicate = + new SomeButtonsDownPredicate(moveConfig.MoveLeft); + moveRightPredicate = + new SomeButtonsDownPredicate(moveConfig.MoveRight); + moveForwardPredicate = + new SomeButtonsDownPredicate(moveConfig.MoveForward); + moveBackPredicate = + new SomeButtonsDownPredicate(moveConfig.MoveBack); + turnLeftPredicate = + new SomeButtonsDownPredicate(moveConfig.TurnLeft); + turnRightPredicate = + new SomeButtonsDownPredicate(moveConfig.TurnRight); + turnUpPredicate = + new SomeButtonsDownPredicate(moveConfig.TurnUp); + turnDownPredicate = + new SomeButtonsDownPredicate(moveConfig.TurnDown); + + moveTrigger = CreateMoveTrigger(); + + mouseTurnPredicate = CreateMouseTurnPredicate( + mouseLookDragOnly); + + turnTrigger = CreateTurnTrigger(); + } + + /** + * Create a trigger to map move keys + * to the move the player + * @return + * The trigger. + */ + private InputTrigger CreateMoveTrigger() + { + + final UprightFlyControlTriggers control = this; + + final Predicate<TwoInputStates> keysHeld = + moveConfig.createSinglePredicate(moveConfig.createSomeMoveKeysDownPredicates()); + + final TriggerAction moveAction = new TriggerAction() { + @Override + public void perform(final Canvas source, + final TwoInputStates inputStates, final double tpf) { + control.move(inputStates, tpf); + } + }; + + return new InputTrigger(keysHeld, moveAction); + } + + /** + * Create a predicate for mouse movements + * @param mouseTurnDragOnly + * True if looking requres + * dragging + * @return + * The predicate for mouse turning. + */ + private Predicate<TwoInputStates> CreateMouseTurnPredicate( + boolean mouseTurnDragOnly) + { + + // Mouse look + final Predicate<TwoInputStates> someMouseDown = + TriggerConditions.leftButtonDown().or(TriggerConditions.rightButtonDown()).or(TriggerConditions.middleButtonDown()); + final Predicate<TwoInputStates> dragged = TriggerConditions.mouseMoved().and(someMouseDown); + + final Predicate<TwoInputStates> mouseLookUpdate = + mouseTurnDragOnly + ? dragged + : TriggerConditions.mouseMoved(); + + return mouseLookUpdate; + + } + + /** + * Creates a trigger for managing looking + * and turning. + * @param mouseLookDragOnly + * @return + * The trigger for the player + * to look and turn. + */ + private InputTrigger CreateTurnTrigger() + { + + final UprightFlyControlTriggers control = this; + + final Predicate<TwoInputStates> keysHeld = + moveConfig.createSinglePredicate(moveConfig.createSomeTurnKeyPredicates()); + + final Predicate<TwoInputStates> turnPredicate = keysHeld.or(mouseTurnPredicate); + + final TriggerAction turnAction = new TriggerAction() { + + @Override + public void perform(final Canvas source, + final TwoInputStates inputStates, final double tpf) { + control.turn(inputStates, tpf); + } + }; + + return new InputTrigger(turnPredicate, turnAction); + + } + + /** + * Add the triggers to the logical layer + */ + @Override + public void AddToLayer(LogicalLayer layer) + { + layer.registerTrigger(moveTrigger); + layer.registerTrigger(turnTrigger); + } + + /** + * Remove the triggers from the logical layer + */ + @Override + public void RemoveFromLayer(LogicalLayer layer) + { + layer.deregisterTrigger(moveTrigger); + layer.deregisterTrigger(turnTrigger); + } + + /** + * Move the player according to the + * triggers + * @param state + * The input state. + * @param tpf + * The time per frame. + */ + protected void move( + final TwoInputStates state, + final double tpf) + { + // MOVEMENT + int moveFB = 0, strafeLR = 0; + if (moveForwardPredicate.test(state)) { + moveFB += 1; + } + if (moveBackPredicate.test(state)) { + moveFB -= 1; + } + if (moveLeftPredicate.test(state)) { + strafeLR += 1; + } + if (moveRightPredicate.test(state)) { + strafeLR -= 1; + } + + if (moveFB != 0 || strafeLR != 0) { + final Vector3 loc = new Vector3(Vector3.ZERO); + if (moveFB == 1) { + loc.addLocal(player.getDirection()); + } else if (moveFB == -1) { + loc.subtractLocal(player.getDirection()); + } + if (strafeLR == 1) { + loc.addLocal(player.getLeft()); + } else if (strafeLR == -1) { + loc.subtractLocal(player.getLeft()); + } + loc.normalizeLocal().multiplyLocal(moveSpeed * tpf) + .addLocal(player.getPosition()); + player.getPosition().set(loc); + } + } + + /** + * Turns the player + * @param state + * The input state for the + * turn + * @param tpf + * The time per frame for + * the last frame. + */ + protected void turn( + final TwoInputStates state, + final double tpf) + { + + final double keyboardRotationFactor = + keyRotateSpeed / mouseRotateSpeed * tpf; + + // ROTATION + double rotX = 0, rotY = 0; + + if (turnUpPredicate.test(state)) { + rotY -= keyboardRotationFactor; + } + if (turnDownPredicate.test(state)) { + rotY += keyboardRotationFactor; + } + if (turnLeftPredicate.test(state)) { + rotX += keyboardRotationFactor; + } + if (turnRightPredicate.test(state)) { + rotX -= keyboardRotationFactor; + } + + if(mouseTurnPredicate.test(state)) + { + if(firstPing) + { + firstPing = false; + } + else + { + final MouseState mouse = state.getCurrent() + .getMouseState(); + rotX -= mouse.getDx(); + rotY -= mouse.getDy(); + } + } + + rotate(rotX, rotY); + } + + /** + * Rotate the player by the desired + * amounts + * @param dx + * The x angle to rotate + * @param dy + * The y angle to rotate + */ + protected void rotate(final double dx, final double dy) { + + if (dx != 0) { + workerMatrix.fromAngleNormalAxis(mouseRotateSpeed * dx, + upAxis != null ? upAxis : player.getUp()); + workerMatrix.applyPost(player.getLeft(), workerStoreA); + player.getLeft().set(workerStoreA); + workerMatrix.applyPost(player.getDirection(), workerStoreA); + player.getDirection().set(workerStoreA); + workerMatrix.applyPost(player.getUp(), workerStoreA); + player.getUp().set(workerStoreA); + } + + if (dy != 0) { + workerMatrix.fromAngleNormalAxis(mouseRotateSpeed * dy, + player.getLeft()); + workerMatrix.applyPost(player.getLeft(), workerStoreA); + player.getLeft().set(workerStoreA); + workerMatrix.applyPost(player.getDirection(), workerStoreA); + player.getDirection().set(workerStoreA); + workerMatrix.applyPost(player.getUp(), workerStoreA); + player.getUp().set(workerStoreA); + } + + player.normalize(); + + } + +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControl.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControl.java new file mode 100644 index 0000000..cc77dbd --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControl.java @@ -0,0 +1,318 @@ +/** + * 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.ardorcraft.control; + +import java.util.function.Predicate; + +import com.ardor3d.framework.Canvas; +//import com.ardor3d.input.Key; +//import com.ardor3d.input.KeyboardState; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.InputTrigger; +import com.ardor3d.input.logical.LogicalLayer; +import com.ardor3d.input.logical.TriggerAction; +import com.ardor3d.input.logical.TriggerConditions; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardorcraft.player.PlayerBase; + +public class WalkControl { + + private final Vector3 _upAxis = new Vector3(); + private double _mouseRotateSpeed = .003; + private double _moveSpeed = 0.8; + private double _keyRotateSpeed = 2.25; + private final Matrix3 _workerMatrix = new Matrix3(); + private final Vector3 _workerStoreA = new Vector3(); + private InputTrigger _mouseTrigger; + private InputTrigger _keyTrigger; + private final LogicalLayer layer; + private final PlayerBase player; + + private SomeButtonsDownPredicate moveLeftPredicate; + private SomeButtonsDownPredicate moveRightPredicate; + private SomeButtonsDownPredicate moveForwardPredicate; + private SomeButtonsDownPredicate moveBackPredicate; + private SomeButtonsDownPredicate turnLeftPredicate; + private SomeButtonsDownPredicate turnRightPredicate; + private SomeButtonsDownPredicate turnUpPredicate; + private SomeButtonsDownPredicate turnDownPredicate; + private UprightFPSMoveConfig moveConfigMember; + + public WalkControl(final PlayerBase player, final LogicalLayer layer, + final ReadOnlyVector3 upAxis, UprightFPSMoveConfig moveConfigParam) { + _upAxis.set(upAxis); + this.layer = layer; + this.player = player; + + moveConfigMember = moveConfigParam; + moveLeftPredicate = + new SomeButtonsDownPredicate(moveConfigMember.MoveLeft); + moveRightPredicate = + new SomeButtonsDownPredicate(moveConfigMember.MoveRight); + moveForwardPredicate = + new SomeButtonsDownPredicate(moveConfigMember.MoveForward); + moveBackPredicate = + new SomeButtonsDownPredicate(moveConfigMember.MoveBack); + turnLeftPredicate = + new SomeButtonsDownPredicate(moveConfigMember.TurnLeft); + turnRightPredicate = + new SomeButtonsDownPredicate(moveConfigMember.TurnRight); + turnUpPredicate = + new SomeButtonsDownPredicate(moveConfigMember.TurnUp); + turnDownPredicate = + new SomeButtonsDownPredicate(moveConfigMember.TurnDown); + } + + public ReadOnlyVector3 getUpAxis() { + return _upAxis; + } + + public void setUpAxis(final ReadOnlyVector3 upAxis) { + _upAxis.set(upAxis); + } + + public double getMouseRotateSpeed() { + return _mouseRotateSpeed; + } + + public void setMouseRotateSpeed(final double speed) { + _mouseRotateSpeed = speed; + } + + public double getMoveSpeed() { + return _moveSpeed; + } + + public void setMoveSpeed(final double speed) { + _moveSpeed = speed; + } + + public double getKeyRotateSpeed() { + return _keyRotateSpeed; + } + + public void setKeyRotateSpeed(final double speed) { + _keyRotateSpeed = speed; + } + + protected void move( + final TwoInputStates state, + final double tpf) { + // MOVEMENT + int moveFB = 0, strafeLR = 0; + if (moveForwardPredicate.test(state)) { + moveFB += 1; + } + if (moveBackPredicate.test(state)) { + moveFB -= 1; + } + if (moveLeftPredicate.test(state)) { + strafeLR += 1; + } + if (moveRightPredicate.test(state)) { + strafeLR -= 1; + } + + if (moveFB != 0 || strafeLR != 0) { + final Vector3 loc = _workerStoreA.zero(); + if (moveFB == 1) { + loc.addLocal(player.getDirection()); + } else if (moveFB == -1) { + loc.subtractLocal(player.getDirection()); + } + if (strafeLR == 1) { + loc.addLocal(player.getLeft()); + } else if (strafeLR == -1) { + loc.subtractLocal(player.getLeft()); + } + loc.setY(0); + loc.normalizeLocal(); + player.getAcceleration().set(loc).multiplyLocal(_moveSpeed); + + // TODO + // player.getTargetVelocity().x = loc.getX() * 80 * + // PhysicsSystem.TIMESTEP; + // player.getTargetVelocity().z = loc.getZ() * 80 * + // PhysicsSystem.TIMESTEP; + } + + // ROTATION + int rotX = 0, rotY = 0; + if (turnUpPredicate.test(state)) { + rotY -= 1; + } + if (turnDownPredicate.test(state)) { + rotY += 1; + } + if (turnLeftPredicate.test(state)) { + rotX += 1; + } + if (turnRightPredicate.test(state)) { + rotX -= 1; + } + if (rotX != 0 || rotY != 0) { + rotate(rotX * _keyRotateSpeed / _mouseRotateSpeed * tpf, rotY + * _keyRotateSpeed / _mouseRotateSpeed * tpf); + } + } + + protected void rotate(final double dx, final double dy) { + + if (dx != 0) { + _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dx, + _upAxis != null ? _upAxis : player.getUp()); + _workerMatrix.applyPost(player.getLeft(), _workerStoreA); + player.getLeft().set(_workerStoreA); + _workerMatrix.applyPost(player.getDirection(), _workerStoreA); + player.getDirection().set(_workerStoreA); + _workerMatrix.applyPost(player.getUp(), _workerStoreA); + player.getUp().set(_workerStoreA); + } + + if (dy != 0) { + _workerMatrix.fromAngleNormalAxis(_mouseRotateSpeed * dy, + player.getLeft()); + _workerMatrix.applyPost(player.getLeft(), _workerStoreA); + player.getLeft().set(_workerStoreA); + _workerMatrix.applyPost(player.getDirection(), _workerStoreA); + player.getDirection().set(_workerStoreA); + _workerMatrix.applyPost(player.getUp(), _workerStoreA); + player.getUp().set(_workerStoreA); + } + + player.normalize(); + } + + /** + * @param layer + * the logical layer to register with + * @param upAxis + * the up axis of the camera + * @param dragOnly + * if true, mouse input will only rotate the camera if one of the + * mouse buttons (left, center or right) is down. + * @return a new FlyControl object + */ + public static WalkControl setupTriggers(final PlayerBase player, + final LogicalLayer layer, final ReadOnlyVector3 upAxis, + final boolean dragOnly) { + + final WalkControl control = + new WalkControl( + player, + layer, + upAxis, + new UprightFPSMoveConfig(UprightFPSMoveConfig.defaultControls.RightHanded)); + control.setupKeyboardTriggers(layer); + control.setupMouseTriggers(layer, dragOnly); + return control; + } + + /** + * Deregister the triggers of the given FlyControl from the given + * LogicalLayer. + * + * @param layer + * @param control + */ + public static void removeTriggers(final LogicalLayer layer, + final WalkControl control) { + if (control._mouseTrigger != null) { + layer.deregisterTrigger(control._mouseTrigger); + } + if (control._keyTrigger != null) { + layer.deregisterTrigger(control._keyTrigger); + } + } + + public void setupMouseTriggers(final LogicalLayer layer, + final boolean dragOnly) { + final WalkControl control = this; + // Mouse look + final Predicate<TwoInputStates> someMouseDown = + TriggerConditions.leftButtonDown().or(TriggerConditions.rightButtonDown().or(TriggerConditions.middleButtonDown())); + final Predicate<TwoInputStates> dragged = TriggerConditions.mouseMoved().and(someMouseDown); + final TriggerAction dragAction = new TriggerAction() { + + // Test boolean to allow us to ignore first mouse event. First event + // can wildly vary based on platform. + private boolean firstPing = true; + + @Override + public void perform(final Canvas source, + final TwoInputStates inputStates, final double tpf) { + final MouseState mouse = inputStates.getCurrent() + .getMouseState(); + if (mouse.getDx() != 0 || mouse.getDy() != 0) { + if (!firstPing) { + control.rotate(-mouse.getDx(), -mouse.getDy()); + } else { + firstPing = false; + } + } + } + }; + + _mouseTrigger = new InputTrigger(dragOnly ? dragged + : TriggerConditions.mouseMoved(), dragAction); + layer.registerTrigger(_mouseTrigger); + } + + public Predicate<TwoInputStates> setupKeyboardTriggers( + final LogicalLayer layer) { + + final WalkControl control = this; + + // WASD control + final Predicate<TwoInputStates> keysHeld = + moveConfigMember.createSinglePredicate(moveConfigMember.createKeysDownPredicates()); + + final TriggerAction moveAction = new TriggerAction() { + @Override + public void perform(final Canvas source, + final TwoInputStates inputStates, final double tpf) { + control.move(inputStates, tpf); + } + }; + _keyTrigger = new InputTrigger(keysHeld, moveAction); + layer.registerTrigger(_keyTrigger); + return keysHeld; + } + + public InputTrigger getKeyTrigger() { + return _keyTrigger; + } + + public InputTrigger getMouseTrigger() { + return _mouseTrigger; + } + + public void enable() { + if (!layer.getTriggers().contains(_keyTrigger)) { + layer.registerTrigger(_keyTrigger); + } + if (!layer.getTriggers().contains(_mouseTrigger)) { + layer.registerTrigger(_mouseTrigger); + } + } + + public void disable() { + if (layer.getTriggers().contains(_keyTrigger)) { + layer.deregisterTrigger(_keyTrigger); + } + if (layer.getTriggers().contains(_mouseTrigger)) { + layer.deregisterTrigger(_mouseTrigger); + } + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControlTriggers.java b/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControlTriggers.java new file mode 100644 index 0000000..0483105 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/control/WalkControlTriggers.java @@ -0,0 +1,352 @@ +/** + * + */ +package com.ardorcraft.control; + +import java.util.function.Predicate; + +import com.ardor3d.framework.Canvas; +import com.ardor3d.input.MouseState; +import com.ardor3d.input.logical.InputTrigger; +import com.ardor3d.input.logical.LogicalLayer; +import com.ardor3d.input.logical.TriggerAction; +import com.ardor3d.input.logical.TriggerConditions; +import com.ardor3d.input.logical.TwoInputStates; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardorcraft.player.PlayerBase; + +/** + * Set of triggers to use for walking. + * @author nathan + * + */ +public class WalkControlTriggers implements ITriggerGroup +{ + private final Vector3 upAxis = new Vector3(); + private double mouseRotateSpeed = .003; + private double moveSpeed = 0.8; + private double keyRotateSpeed = 2.25; + private final Matrix3 workerMatrix = new Matrix3(); + private final Vector3 workerStoreA = new Vector3(); + private InputTrigger turnTrigger; + private InputTrigger moveTrigger; + private final PlayerBase player; + + // The first mouse state is pretty random, + // so arrange to ignore it. I'm not sure + // this should be there, but I'm not sure + // if Ardor3D allows not firing the + // triggers on the first update. + private boolean firstPing = true; + + private Predicate<TwoInputStates> mouseTurnPredicate; + + private SomeButtonsDownPredicate moveLeftPredicate; + private SomeButtonsDownPredicate moveRightPredicate; + private SomeButtonsDownPredicate moveForwardPredicate; + private SomeButtonsDownPredicate moveBackPredicate; + private SomeButtonsDownPredicate turnLeftPredicate; + private SomeButtonsDownPredicate turnRightPredicate; + private SomeButtonsDownPredicate turnUpPredicate; + private SomeButtonsDownPredicate turnDownPredicate; + private UprightFPSMoveConfig moveConfig; + + /** + * Constructor for the WalkControlTriggers + * @param playerParam + * The player for the triggers + * to update + * @param upAxisParam + * The up axis of the player + * @param mouseLookDragOnly + * True if mouse look requires + * draging + * @param moveConfigParam + * The keys for for moving and turning. + */ + public WalkControlTriggers( + final PlayerBase playerParam, + final ReadOnlyVector3 upAxisParam, + final boolean mouseLookDragOnly, + final UprightFPSMoveConfig moveConfigParam) + { + this.player = playerParam; + upAxis.set(upAxisParam); + moveConfig = moveConfigParam; + + moveLeftPredicate = + new SomeButtonsDownPredicate(moveConfig.MoveLeft); + moveRightPredicate = + new SomeButtonsDownPredicate(moveConfig.MoveRight); + moveForwardPredicate = + new SomeButtonsDownPredicate(moveConfig.MoveForward); + moveBackPredicate = + new SomeButtonsDownPredicate(moveConfig.MoveBack); + turnLeftPredicate = + new SomeButtonsDownPredicate(moveConfig.TurnLeft); + turnRightPredicate = + new SomeButtonsDownPredicate(moveConfig.TurnRight); + turnUpPredicate = + new SomeButtonsDownPredicate(moveConfig.TurnUp); + turnDownPredicate = + new SomeButtonsDownPredicate(moveConfig.TurnDown); + + moveTrigger = CreateMoveTrigger(); + + mouseTurnPredicate = CreateMouseTurnPredicate( + mouseLookDragOnly); + + turnTrigger = CreateTurnTrigger(); + } + + /** + * Create a trigger to map move keys + * to the move the player + * @return + * The trigger. + */ + private InputTrigger CreateMoveTrigger() + { + + final WalkControlTriggers control = this; + + final Predicate<TwoInputStates> keysHeld = + moveConfig.createSinglePredicate(moveConfig.createSomeMoveKeysDownPredicates()); + + final TriggerAction moveAction = new TriggerAction() { + @Override + public void perform(final Canvas source, + final TwoInputStates inputStates, final double tpf) { + control.move(inputStates, tpf); + } + }; + + return new InputTrigger(keysHeld, moveAction); + } + + /** + * Create a predicate for mouse movements + * @param mouseTurnDragOnly + * True if looking requres + * dragging + * @return + * The predicate for mouse turning. + */ + private Predicate<TwoInputStates> CreateMouseTurnPredicate( + boolean mouseTurnDragOnly) + { + + // Mouse look + final Predicate<TwoInputStates> someMouseDown = + TriggerConditions.leftButtonDown().or(TriggerConditions.rightButtonDown().or(TriggerConditions.middleButtonDown())); + final Predicate<TwoInputStates> dragged = TriggerConditions.mouseMoved().and(someMouseDown); + + final Predicate<TwoInputStates> mouseLookUpdate = + mouseTurnDragOnly + ? dragged + : TriggerConditions.mouseMoved(); + + return mouseLookUpdate; + + } + + /** + * Creates a trigger for managing looking + * and turning. + * @param mouseLookDragOnly + * @return + * The trigger for the player + * to look and turn. + */ + private InputTrigger CreateTurnTrigger() + { + + final WalkControlTriggers control = this; + + final Predicate<TwoInputStates> keysHeld = + moveConfig.createSinglePredicate(moveConfig.createSomeTurnKeyPredicates()); + + final Predicate<TwoInputStates> turnPredicate = + keysHeld.or(mouseTurnPredicate); + + final TriggerAction turnAction = new TriggerAction() { + + @Override + public void perform(final Canvas source, + final TwoInputStates inputStates, final double tpf) { + control.turn(inputStates, tpf); + } + }; + + return new InputTrigger(turnPredicate, turnAction); + + } + + /** + * Move in response to the controls + * @param state + * The input state + * @param tpf + * The time per frame. + */ + protected void move(TwoInputStates state, double tpf) + { + // MOVEMENT + int moveFB = 0, strafeLR = 0; + if (moveForwardPredicate.test(state)) + { + moveFB += 1; + } + if (moveBackPredicate.test(state)) + { + moveFB -= 1; + } + if (moveLeftPredicate.test(state)) + { + strafeLR += 1; + } + if (moveRightPredicate.test(state)) + { + strafeLR -= 1; + } + + if (moveFB != 0 || strafeLR != 0) + { + final Vector3 loc = workerStoreA.zero(); + if (moveFB == 1) + { + loc.addLocal(player.getDirection()); + } + else if (moveFB == -1) + { + loc.subtractLocal(player.getDirection()); + } + + if (strafeLR == 1) + { + loc.addLocal(player.getLeft()); + } + else if (strafeLR == -1) + { + loc.subtractLocal(player.getLeft()); + } + loc.setY(0); + loc.normalizeLocal(); + loc.multiplyLocal(moveSpeed); + player.getAcceleration().set(loc); + + // TODO + // player.getTargetVelocity().x = loc.getX() * 80 * + // PhysicsSystem.TIMESTEP; + // player.getTargetVelocity().z = loc.getZ() * 80 * + // PhysicsSystem.TIMESTEP; + } + } + + /** + * Turn the player in response to + * the input state. + * @param state + * The input state + * @param tpf + * The time per frame. + */ + protected void turn(TwoInputStates state, double tpf) + { + + final double keyboardRotationFactor = + keyRotateSpeed / mouseRotateSpeed * tpf; + + // ROTATION + double rotX = 0, rotY = 0; + + if (turnUpPredicate.test(state)) { + rotY -= keyboardRotationFactor; + } + if (turnDownPredicate.test(state)) { + rotY += keyboardRotationFactor; + } + if (turnLeftPredicate.test(state)) { + rotX += keyboardRotationFactor; + } + if (turnRightPredicate.test(state)) { + rotX -= keyboardRotationFactor; + } + + if(mouseTurnPredicate.test(state)) + { + if(firstPing) + { + firstPing = false; + } + else + { + final MouseState mouse = state.getCurrent() + .getMouseState(); + rotX -= mouse.getDx(); + rotY -= mouse.getDy(); + } + } + + rotate(rotX, rotY); + } + + /** + * Rotate the player by the desired + * amounts + * @param dx + * The x angle to rotate + * @param dy + * The y angle to rotate + */ + protected void rotate(final double dx, final double dy) { + + if (dx != 0) { + workerMatrix.fromAngleNormalAxis(mouseRotateSpeed * dx, + upAxis != null ? upAxis : player.getUp()); + workerMatrix.applyPost(player.getLeft(), workerStoreA); + player.getLeft().set(workerStoreA); + workerMatrix.applyPost(player.getDirection(), workerStoreA); + player.getDirection().set(workerStoreA); + workerMatrix.applyPost(player.getUp(), workerStoreA); + player.getUp().set(workerStoreA); + } + + if (dy != 0) { + workerMatrix.fromAngleNormalAxis(mouseRotateSpeed * dy, + player.getLeft()); + workerMatrix.applyPost(player.getLeft(), workerStoreA); + player.getLeft().set(workerStoreA); + workerMatrix.applyPost(player.getDirection(), workerStoreA); + player.getDirection().set(workerStoreA); + workerMatrix.applyPost(player.getUp(), workerStoreA); + player.getUp().set(workerStoreA); + } + + player.normalize(); + + } + + /* (non-Javadoc) + * @see com.ardorcraft.control.ITriggerGroup#AddToLayer(com.ardor3d.input.logical.LogicalLayer) + */ + @Override + public void AddToLayer(LogicalLayer layer) + { + layer.registerTrigger(moveTrigger); + layer.registerTrigger(turnTrigger); + } + + /* (non-Javadoc) + * @see com.ardorcraft.control.ITriggerGroup#RemoveFromLayer(com.ardor3d.input.logical.LogicalLayer) + */ + @Override + public void RemoveFromLayer(LogicalLayer layer) + { + layer.deregisterTrigger(moveTrigger); + layer.deregisterTrigger(turnTrigger); + } + +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/data/Direction.java b/ardor3d-craft/src/main/java/com/ardorcraft/data/Direction.java new file mode 100644 index 0000000..50520dd --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/data/Direction.java @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.data; + +public enum Direction { + X, Y, Z +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/data/Pos.java b/ardor3d-craft/src/main/java/com/ardorcraft/data/Pos.java new file mode 100644 index 0000000..a9e8feb --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/data/Pos.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.data; + +/** + * Integer 3D coordinate + */ +public class Pos { + public int x, y, z; + + public Pos() { + + } + + public Pos(final Pos pos) { + this(pos.x, pos.y, pos.z); + } + + public Pos(final int x, final int y, final int z) { + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + x; + result = prime * result + y; + result = prime * result + z; + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Pos)) { + return false; + } + final Pos other = (Pos) obj; + if (x != other.x) { + return false; + } + if (y != other.y) { + return false; + } + if (z != other.z) { + return false; + } + return true; + } + + @Override + public String toString() { + return "Pos [x=" + x + ", y=" + y + ", z=" + z + "]"; + } + + public void set(final int x, final int y, final int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void set(final Pos pos) { + set(pos.x, pos.y, pos.z); + } + + public void add(final int x, final int y, final int z) { + this.x += x; + this.y += y; + this.z += z; + } + + public void subtract(final int x, final int y, final int z) { + this.x -= x; + this.y -= y; + this.z -= z; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFile.java b/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFile.java new file mode 100644 index 0000000..28c8330 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFile.java @@ -0,0 +1,313 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.file; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Logger; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +/** + * ArdorCraft map file format + * <p> + * + * Layout of each chunk data in the file: + * <ul> + * <li>byte 0 = this key (long, 8) + * <li>byte 8 = next key pos (long, 8) + * <li>byte 16 = this pos (long, 8 (0 = +4, >0 = to start of next "this pos")) + * <li>byte 24 = data size (int, 4) + * <li>byte 28 = compressed data (byte[]) + * + */ +public final class WorldFile { + private static final Logger logger = Logger.getLogger(WorldFile.class.getName()); + + private final RandomAccessFile worldFile; + private final Map<Long, Long> mapping = new HashMap<>(); + private final Lock lock = new ReentrantLock(); + private final Deflater deflator = new Deflater(); + private final Inflater inflator = new Inflater(); + private final byte[] buf = new byte[1024 * 1024]; + private static final int IDENTIFIER = "ArdorCraft Map".hashCode(); + private static final int VERSION = 1; + + public WorldFile(final File file) throws Exception { + worldFile = new RandomAccessFile(file, "rw"); + parse(); + } + + public void close() throws IOException { + lock.lock(); + try { + deflator.end(); + inflator.end(); + worldFile.close(); + } finally { + lock.unlock(); + } + } + + public long size() throws IOException { + return worldFile.length(); + } + + public boolean contains(final int x, final int z) { + lock.lock(); + try { + return mapping.containsKey(getKey(x, z)); + } finally { + lock.unlock(); + } + } + + private void parse() throws IOException { + lock.lock(); + try { + final long endPos = worldFile.length(); + + worldFile.seek(0); + if (endPos == 0) { + worldFile.writeInt(IDENTIFIER); + worldFile.writeInt(VERSION); + return; + } + if (endPos >= 4) { + final boolean isMapFile = worldFile.readInt() == IDENTIFIER; + if (!isMapFile) { + throw new RuntimeException("This is not an ArdorCraft map file!"); + } + } + if (endPos >= 8) { + final int version = worldFile.readInt(); + if (version < VERSION) { + throw new RuntimeException("Map file was created with an older version of the ArdorCraft API (" + + version + " < " + VERSION + ")"); + } + logger.info("Map file version: " + version); + } + + long pos = 8; + while (true) { + if (pos >= endPos) { + logger.info("Map file contains " + mapping.keySet().size() + " chunks"); + return; + } + worldFile.seek(pos); + final long key = worldFile.readLong(); + final long nextKeyPos = worldFile.readLong(); + + if (nextKeyPos < 0 || nextKeyPos > endPos) { + logger.warning("Corrupt mapping, nextKeyPos: " + nextKeyPos); + return; + } + + if (mapping.containsKey(key)) { + pos = nextKeyPos; + continue; + } + + long thisPos = worldFile.readLong(); + while (thisPos > 0) { + worldFile.seek(thisPos); + thisPos = worldFile.readLong(); + } + + mapping.put(key, worldFile.getFilePointer() - 24); + pos = nextKeyPos; + + // final int x = WorldFile.getCoordinateX(key); + // final int z = WorldFile.getCoordinateZ(key); + // System.out.println("parsed: " + x + "," + z + " = " + (worldFile.getFilePointer() - 24)); + } + } finally { + lock.unlock(); + } + } + + public void remap() throws IOException { + lock.lock(); + try { + // File file = File.createTempFile("foo", null); + final File file = new File("tmpFile.tmp"); + if (file.exists()) { + file.delete(); + } + try (final RandomAccessFile tmpFile = new RandomAccessFile(file, "rw")) { + + tmpFile.writeInt(IDENTIFIER); + tmpFile.writeInt(VERSION); + for (final Entry<Long, Long> entry : mapping.entrySet()) { + final long key = entry.getKey(); + final long pos = entry.getValue(); + + worldFile.seek(pos + 24); + final int size = worldFile.readInt(); + + final long endPos = tmpFile.getFilePointer(); + + tmpFile.writeLong(key); + tmpFile.writeLong(endPos + 28 + size); + tmpFile.writeLong(0); + tmpFile.writeInt(size); + + long transfered = 0; + while (transfered < size) { + transfered += worldFile.getChannel().transferTo(pos + 28 + transfered, size - transfered, + tmpFile.getChannel()); + } + tmpFile.seek(endPos + 28 + size); + } + + worldFile.seek(0); + final long size = tmpFile.length(); + long transfered = 0; + while (transfered < size) { + transfered += tmpFile.getChannel().transferTo(transfered, size - transfered, worldFile.getChannel()); + } + worldFile.setLength(size); + + } finally { + file.delete(); + } + } finally { + lock.unlock(); + } + } + + public void save(final int x, final int z, final byte[] dataSource) throws IOException { + lock.lock(); + try { + final long newKey = getKey(x, z); + final long endPosition = worldFile.length(); + + final byte[] data = compress(dataSource); + final int length = data.length; + + if (!mapping.containsKey(newKey)) { + worldFile.seek(endPosition); + worldFile.writeLong(newKey); + worldFile.writeLong(endPosition + 28 + length); + worldFile.writeLong(0); + worldFile.writeInt(length); + worldFile.write(data); + + mapping.put(newKey, endPosition); + } else { + final long pos = mapping.get(newKey); + + // read size + worldFile.seek(pos + 24); + final int size = worldFile.readInt(); + if (size < length) { + worldFile.seek(pos + 16); + worldFile.writeLong(endPosition + 16); + + worldFile.seek(endPosition); + worldFile.writeLong(newKey); + worldFile.writeLong(endPosition + 28 + length); + worldFile.writeLong(0); + worldFile.writeInt(length); + worldFile.write(data); + + mapping.put(newKey, endPosition); + } else { + worldFile.seek(pos + 24); + worldFile.writeInt(length); + worldFile.write(data); + } + } + } finally { + lock.unlock(); + } + } + + public byte[] load(final int x, final int z) throws IOException { + final long newKey = getKey(x, z); + lock.lock(); + try { + if (!mapping.containsKey(newKey)) { + logger.severe("No data found for coords: " + x + "," + z); + return null; + } + long pos = mapping.get(newKey); + + pos += 24; // go to size + worldFile.seek(pos); + final int size = worldFile.readInt(); + final byte[] data = new byte[size]; + worldFile.read(data); + + return decompress(data); + } finally { + lock.unlock(); + } + } + + public static long getKey(final int x, final int z) { + long r = x; + r <<= 32; + r |= z & 0XFFFFFFFFL; + return r; + } + + public static int getCoordinateX(final long key) { + return (int) (key >> 32 & 0XFFFFFFFF); + } + + public static int getCoordinateZ(final long key) { + return (int) (key & 0XFFFFFFFF); + } + + private byte[] compress(final byte[] data) { + deflator.reset(); + deflator.setInput(data); + deflator.finish(); + final ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length); + while (!deflator.finished()) { + final int count = deflator.deflate(buf); + if (count > 0) { + bos.write(buf, 0, count); + } + } + return bos.toByteArray(); + } + + private byte[] decompress(final byte[] input) { + inflator.reset(); + inflator.setInput(input); + final ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length); + try { + while (true) { + final int count = inflator.inflate(buf); + if (count > 0) { + bos.write(buf, 0, count); + } else if (count == 0 && inflator.finished()) { + break; + } else { + throw new RuntimeException("bad zip data, size:" + input.length); + } + } + } catch (final DataFormatException t) { + throw new RuntimeException(t); + } + return bos.toByteArray(); + } + + Map<Long, Long> getMapping() { + return mapping; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFileViewer.java b/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFileViewer.java new file mode 100644 index 0000000..6cf474a --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/file/WorldFileViewer.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.file; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.io.File; +import java.util.Map; +import java.util.Random; + +import javax.swing.JFrame; +import javax.swing.JPanel; + +import com.ardor3d.math.ColorRGBA; + +public class WorldFileViewer { + private final Graphics2D g2; + private final ColorRGBA[] colors; + private final ColorRGBA[] heightCol; + private final Color[] heightCol2; + + private final int subMeshSize; + private final int height; + private final int subsize = 1; + private final int size = 16 * subsize; + + private final WorldFile worldFile; + + public WorldFileViewer(final File file, final int subMeshSize, final int height) throws Exception { + worldFile = new WorldFile(file); + this.subMeshSize = subMeshSize; + this.height = height; + + final JPanel panel = new JPanel(); + final JFrame frame = new JFrame("WorldFile Viewer"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.getContentPane().add(panel); + frame.setBounds(10, 10, 1000, 1000); + frame.setVisible(true); + g2 = (Graphics2D) panel.getGraphics(); + + final Random rand = new Random(1337); + colors = new ColorRGBA[255]; + for (int i = 0; i < 255; i++) { + colors[i] = new ColorRGBA(rand.nextFloat(), rand.nextFloat(), rand.nextFloat(), 1); + } + + heightCol = new ColorRGBA[100]; + heightCol2 = new Color[100]; + for (int i = 0; i < 100; i++) { + final float v = i / 100.0f * 0.9f + 0.1f; + heightCol[i] = new ColorRGBA(v, v, v, 1); + heightCol2[i] = new Color(v, v, v); + } + } + + public void view() throws Exception { + final Map<Long, Long> mapping = worldFile.getMapping(); + for (final Long key : mapping.keySet()) { + final int x = WorldFile.getCoordinateX(key); + final int z = WorldFile.getCoordinateZ(key); + final byte[] dataSource = worldFile.load(x, z); + paintChunk(x, z, dataSource); + } + } + + private void paintChunk(final int x, final int z, final byte[] dataSource) { + final int xPos = x * size + 400; + final int yPos = z * size + 400; + + for (int xx = 0; xx < subMeshSize; xx++) { + for (int zz = 0; zz < subMeshSize; zz++) { + for (int yy = height - 1; yy >= 0; yy--) { + final int b = dataSource[xx + (yy + zz * height) * subMeshSize] & 0xff; + + if (b != 0) { + // g2.setColor(colors[b]); + g2.setColor(heightCol2[yy]); + + // final ColorRGBA c1 = colors[b]; + // final ColorRGBA c2 = heightCol[yy]; + // g2.setColor(new Color(c1.getRed() * c2.getRed(), c1.getGreen() * c2.getGreen(), c1.getBlue() + // * c2.getBlue())); + + g2.fillRect(xPos + xx * subsize, yPos + zz * subsize, subsize, subsize); + break; + } + } + } + } + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/generators/DataGenerator.java b/ardor3d-craft/src/main/java/com/ardorcraft/generators/DataGenerator.java new file mode 100644 index 0000000..49d8b32 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/generators/DataGenerator.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.generators; + +import com.ardorcraft.world.WorldModifier; + +/** + * A DataGenerator is responsible for generating the terrain in the world. It HAS to be thread safe! + */ +public interface DataGenerator { + + /** + * Method to implement for custom terrain generation. + * + * @param xStart + * Starting point of generation in X + * @param zStart + * Starting point of generation in ZZ + * @param xEnd + * Ending point of generation in X + * @param zEnd + * Ending point of generation in Z + * @param spacing + * Space between sample data points (for faster zoom outs in map viewers etc) + * @param height + * Height of data to generate in Y + * @param proxy + * WorldModifier to build the world against (setBlock etc). + */ + void generateChunk(final int xStart, final int zStart, final int xEnd, final int zEnd, int spacing, + final int height, final WorldModifier proxy); + +}
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/generators/DefaultDataGenerator.java b/ardor3d-craft/src/main/java/com/ardorcraft/generators/DefaultDataGenerator.java new file mode 100644 index 0000000..c4d40fe --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/generators/DefaultDataGenerator.java @@ -0,0 +1,218 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.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.util.ImprovedNoise; +import com.ardorcraft.world.BlockWorld; +import com.ardorcraft.world.WorldModifier; + +public class DefaultDataGenerator 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-craft/src/main/java/com/ardorcraft/objects/QuadBox.java b/ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadBox.java new file mode 100644 index 0000000..6d3469d --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadBox.java @@ -0,0 +1,333 @@ +/** + * 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.ardorcraft.objects; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * <code>QuadBox</code> is an axis-aligned rectangular prism defined by a center point and x, y, and z extents from that + * center (essentially radii.) + */ +public class QuadBox extends Mesh { + + private double _xExtent, _yExtent, _zExtent; + + private final Vector3 _center = new Vector3(0, 0, 0); + + /** + * Constructs a new 1x1x1 <code>QuadBox</code>. + */ + public QuadBox() { + this("unnamed QuadBox"); + } + + /** + * Constructs a new 1x1x1 <code>QuadBox</code> with the given name. + * + * @param name + * the name to give this new box. This is required for identification and comparison purposes. + */ + public QuadBox(final String name) { + super(name); + setData(Vector3.ZERO, 0.5, 0.5, 0.5); + } + + /** + * Constructs a new <code>QuadBox</code> object using the given two points as opposite corners of the box. These two + * points may be in any order. + * + * @param name + * the name to give this new box. This is required for identification and comparison purposes. + * @param pntA + * the first point + * @param pntB + * the second point. + */ + public QuadBox(final String name, final ReadOnlyVector3 pntA, final ReadOnlyVector3 pntB) { + super(name); + setData(pntA, pntB); + } + + /** + * Constructs a new <code>QuadBox</code> object using the given center and extents. Since the extents represent the + * distance from the center of the box to the edge, the full length of a side is actually 2 * extent. + * + * @param name + * the name to give this new box. This is required for identification and comparison purposes. + * @param center + * Center of the box. + * @param xExtent + * x extent of the box + * @param yExtent + * y extent of the box + * @param zExtent + * z extent of the box + */ + public QuadBox(final String name, final ReadOnlyVector3 center, final double xExtent, final double yExtent, + final double zExtent) { + super(name); + setData(center, xExtent, yExtent, zExtent); + } + + /** + * @return the current center of this box. + */ + public ReadOnlyVector3 getCenter() { + return _center; + } + + /** + * @return the current X extent of this box. + */ + public double getXExtent() { + return _xExtent; + } + + /** + * @return the current Y extent of this box. + */ + public double getYExtent() { + return _yExtent; + } + + /** + * @return the current Z extent of this box. + */ + public double getZExtent() { + return _zExtent; + } + + /** + * Updates the center point and extents of this box to match an axis-aligned box defined by the two given opposite + * corners. + * + * @param pntA + * the first point + * @param pntB + * the second point. + */ + public void setData(final ReadOnlyVector3 pntA, final ReadOnlyVector3 pntB) { + _center.set(pntB).addLocal(pntA).multiplyLocal(0.5); + + final double x = Math.abs(pntB.getX() - _center.getX()); + final double y = Math.abs(pntB.getY() - _center.getY()); + final double z = Math.abs(pntB.getZ() - _center.getZ()); + setData(_center, x, y, z); + } + + /** + * Updates the center point and extents of this box using the defined values. + * + * @param center + * The center of the box. + * @param xExtent + * x extent of the box + * @param yExtent + * y extent of the box + * @param zExtent + * z extent of the box + */ + public void setData(final ReadOnlyVector3 center, final double xExtent, final double yExtent, final double zExtent) { + if (center != null) { + _center.set(center); + } + + _xExtent = xExtent; + _yExtent = yExtent; + _zExtent = zExtent; + + setVertexData(); + setNormalData(); + setTextureData(); + setIndexData(); + + } + + /** + * <code>setVertexData</code> sets the vertex positions that define the box using the center point and defined + * extents. + */ + protected void setVertexData() { + if (_meshData.getVertexBuffer() == null) { + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(24)); + } + + final Vector3[] vert = computeVertices(); // returns 8 + + // Back + BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 0); + BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 1); + BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 2); + BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 3); + + // Right + BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 4); + BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 5); + BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 6); + BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 7); + + // Front + BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 8); + BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 9); + BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 10); + BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 11); + + // Left + BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 12); + BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 13); + BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 14); + BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 15); + + // Top + BufferUtils.setInBuffer(vert[2], _meshData.getVertexBuffer(), 16); + BufferUtils.setInBuffer(vert[6], _meshData.getVertexBuffer(), 17); + BufferUtils.setInBuffer(vert[7], _meshData.getVertexBuffer(), 18); + BufferUtils.setInBuffer(vert[3], _meshData.getVertexBuffer(), 19); + + // Bottom + BufferUtils.setInBuffer(vert[0], _meshData.getVertexBuffer(), 20); + BufferUtils.setInBuffer(vert[5], _meshData.getVertexBuffer(), 21); + BufferUtils.setInBuffer(vert[4], _meshData.getVertexBuffer(), 22); + BufferUtils.setInBuffer(vert[1], _meshData.getVertexBuffer(), 23); + } + + /** + * <code>setNormalData</code> sets the normals of each of the box's planes. + */ + private void setNormalData() { + if (_meshData.getNormalBuffer() == null) { + _meshData.setNormalBuffer(BufferUtils.createVector3Buffer(24)); + + // back + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(0).put(0).put(-1); + } + + // right + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(1).put(0).put(0); + } + + // front + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(0).put(0).put(1); + } + + // left + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(-1).put(0).put(0); + } + + // top + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(0).put(1).put(0); + } + + // bottom + for (int i = 0; i < 4; i++) { + _meshData.getNormalBuffer().put(0).put(-1).put(0); + } + } + } + + /** + * <code>setTextureData</code> sets the points that define the texture of the box. It's a one-to-one ratio, where + * each plane of the box has it's own copy of the texture. That is, the texture is repeated one time for each six + * faces. + */ + private void setTextureData() { + if (_meshData.getTextureCoords(0) == null) { + _meshData.setTextureBuffer(BufferUtils.createVector2Buffer(24), 0); + final FloatBuffer tex = _meshData.getTextureBuffer(0); + + for (int i = 0; i < 6; i++) { + tex.put(1).put(0); + tex.put(0).put(0); + tex.put(0).put(1); + tex.put(1).put(1); + } + } + } + + /** + * <code>setIndexData</code> sets the indices into the list of vertices, defining all triangles that constitute the + * box. + */ + private void setIndexData() { + if (_meshData.getIndexBuffer() == null) { + final byte[] indices = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 + }; + final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length); + buf.put(indices); + buf.rewind(); + _meshData.setIndexBuffer(buf); + _meshData.setIndexMode(IndexMode.Quads); + } + } + + /** + * <code>clone</code> creates a new QuadBox object containing the same data as this one. + * + * @return the new QuadBox + */ + @Override + public QuadBox clone() { + final QuadBox rVal = new QuadBox(getName() + "_clone", _center.clone(), _xExtent, _yExtent, _zExtent); + return rVal; + } + + /** + * @return a size 8 array of Vectors representing the 8 points of the box. + */ + public Vector3[] computeVertices() { + + final Vector3 rVal[] = new Vector3[8]; + rVal[0] = _center.add(-_xExtent, -_yExtent, -_zExtent, null); + rVal[1] = _center.add(_xExtent, -_yExtent, -_zExtent, null); + rVal[2] = _center.add(_xExtent, _yExtent, -_zExtent, null); + rVal[3] = _center.add(-_xExtent, _yExtent, -_zExtent, null); + rVal[4] = _center.add(_xExtent, -_yExtent, _zExtent, null); + rVal[5] = _center.add(-_xExtent, -_yExtent, _zExtent, null); + rVal[6] = _center.add(_xExtent, _yExtent, _zExtent, null); + rVal[7] = _center.add(-_xExtent, _yExtent, _zExtent, null); + return rVal; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_xExtent, "xExtent", 0); + capsule.write(_yExtent, "yExtent", 0); + capsule.write(_zExtent, "zExtent", 0); + capsule.write(_center, "center", new Vector3(Vector3.ZERO)); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _xExtent = capsule.readDouble("xExtent", 0); + _yExtent = capsule.readDouble("yExtent", 0); + _zExtent = capsule.readDouble("zExtent", 0); + _center.set((Vector3) capsule.readSavable("center", new Vector3(Vector3.ZERO))); + } +}
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadQuad.java b/ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadQuad.java new file mode 100644 index 0000000..ad2f247 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/objects/QuadQuad.java @@ -0,0 +1,114 @@ +/** + * 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.ardorcraft.objects; + +import java.nio.ByteBuffer; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.util.geom.BufferUtils; + +/** + * <code>QuadQuad</code> defines a four sided, two dimensional shape. The local height of the <code>QuadQuad</code> + * defines it's size about the y-axis, while the width defines the x-axis. The z-axis will always be 0. + */ +public class QuadQuad extends Mesh { + + protected double _width = 0; + protected double _height = 0; + + public QuadQuad() { + + } + + /** + * Constructor creates a new <code>Quade</code> object with the provided width and height. + * + * @param name + * the name of the <code>QuadQuad</code>. + * @param width + * the width of the <code>QuadQuad</code>. + * @param height + * the height of the <code>QuadQuad</code>. + */ + public QuadQuad(final String name, final double width, final double height, final int mode) { + super(name); + initialize(width, height, mode); + } + + /** + * <code>resize</code> changes the width and height of the given quad by altering its vertices. + * + * @param width + * the new width of the <code>QuadQuad</code>. + * @param height + * the new height of the <code>QuadQuad</code>. + */ + public void resize(final double width, final double height, final int mode) { + _width = width; + _height = height; + + _meshData.getVertexBuffer().clear(); + + if (mode == 0) { + _meshData.getVertexBuffer().put(0).put((float) (-width / 2)).put((float) (height / 2)); + _meshData.getVertexBuffer().put(0).put((float) (-width / 2)).put((float) (-height / 2)); + _meshData.getVertexBuffer().put(0).put((float) (width / 2)).put((float) (-height / 2)); + _meshData.getVertexBuffer().put(0).put((float) (width / 2)).put((float) (height / 2)); + } else if (mode == 1) { + _meshData.getVertexBuffer().put((float) (-width / 2)).put(0).put((float) (height / 2)); + _meshData.getVertexBuffer().put((float) (-width / 2)).put(0).put((float) (-height / 2)); + _meshData.getVertexBuffer().put((float) (width / 2)).put(0).put((float) (-height / 2)); + _meshData.getVertexBuffer().put((float) (width / 2)).put(0).put((float) (height / 2)); + } else if (mode == 2) { + _meshData.getVertexBuffer().put((float) (-width / 2)).put((float) (height / 2)).put(0); + _meshData.getVertexBuffer().put((float) (-width / 2)).put((float) (-height / 2)).put(0); + _meshData.getVertexBuffer().put((float) (width / 2)).put((float) (-height / 2)).put(0); + _meshData.getVertexBuffer().put((float) (width / 2)).put((float) (height / 2)).put(0); + } + } + + /** + * <code>initialize</code> builds the data for the <code>QuadQuad</code> object. + * + * @param width + * the width of the <code>QuadQuad</code>. + * @param height + * the height of the <code>QuadQuad</code>. + */ + private void initialize(final double width, final double height, final int mode) { + final int verts = 4; + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts)); + + final byte[] indices = { + 0, 1, 2, 3 + }; + final ByteBuffer buf = BufferUtils.createByteBuffer(indices.length); + buf.put(indices); + buf.rewind(); + _meshData.setIndexBuffer(buf); + + _meshData.setIndexMode(IndexMode.Quads); + + resize(width, height, mode); + + setDefaultColor(ColorRGBA.BLACK); + } + + public double getWidth() { + return _width; + } + + public double getHeight() { + return _height; + } +}
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/objects/SkyDome.java b/ardor3d-craft/src/main/java/com/ardorcraft/objects/SkyDome.java new file mode 100644 index 0000000..3baca8f --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/objects/SkyDome.java @@ -0,0 +1,350 @@ +/** + * 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.ardorcraft.objects; + +import java.io.IOException; + +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.CullState; +import com.ardor3d.renderer.state.CullState.Face; +import com.ardor3d.renderer.state.FogState; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.hint.CullHint; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; +import com.ardor3d.util.geom.BufferUtils; + +/** + * A half sphere. + */ +public class SkyDome extends Mesh { + + private int _planes; + + private int _radialSamples; + + /** The radius of the dome */ + private double _radius; + + private final ColorRGBA midColor = 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 final ColorRGBA tmpColor = new ColorRGBA(); + + public SkyDome() {} + + /** + * Constructs a dome. By default the dome has not geometry data or center. + * + * @param name + * The name of the dome. + */ + public SkyDome(final String name) { + super(name); + } + + /** + * Constructs a dome with center at the origin. For details, see the other constructor. + * + * @param name + * Name of dome. + * @param planes + * The number of planes along the Z-axis. + * @param radialSamples + * The samples along the radial. + * @param radius + * Radius of the dome. + */ + public SkyDome(final String name, final int planes, final int radialSamples, final double radius) { + this(name, new Vector3(0, 0, 0), planes, radialSamples, radius); + } + + /** + * Constructs a dome. All geometry data buffers are updated automatically. Both planes and radialSamples increase + * the quality of the generated dome. + * + * @param name + * Name of the dome. + * @param center + * Center of the dome. + * @param planes + * The number of planes along the Z-axis. + * @param radialSamples + * The number of samples along the radial. + * @param radius + * The radius of the dome. + */ + public SkyDome(final String name, final Vector3 center, final int planes, final int radialSamples, + final double radius) { + + super(name); + setData(center, planes, radialSamples, radius, true, false); + } + + /** + * Constructs a dome. All geometry data buffers are updated automatically. Both planes and radialSamples increase + * the quality of the generated dome. + * + * @param name + * Name of the dome. + * @param center + * Center of the dome. + * @param planes + * The number of planes along the Z-axis. + * @param radialSamples + * The number of samples along the radial. + * @param radius + * The radius of the dome. + * @param outsideView + * If true, the triangles will be connected for a view outside of the dome. + */ + public SkyDome(final String name, final Vector3 center, final int planes, final int radialSamples, + final double radius, final boolean outsideView) { + super(name); + setData(center, planes, radialSamples, radius, true, outsideView); + } + + /** + * Changes the information of the dome into the given values. The boolean at the end signals if buffer data should + * be updated as well. If the dome is to be rendered, then that value should be true. + * + * @param center + * The new center of the dome. + * @param planes + * The number of planes along the Z-axis. + * @param radialSamples + * The new number of radial samples of the dome. + * @param radius + * The new radius of the dome. + * @param updateBuffers + * If true, buffer information is updated as well. + * @param outsideView + * If true, the triangles will be connected for a view outside of the dome. + */ + public void setData(final Vector3 center, final int planes, final int radialSamples, final double radius, + final boolean updateBuffers, final boolean outsideView) { + _planes = planes; + _radialSamples = radialSamples; + _radius = radius; + + getSceneHints().setLightCombineMode(LightCombineMode.Off); + getSceneHints().setCullHint(CullHint.Never); + getSceneHints().setRenderBucketType(RenderBucketType.Skip); + + final ZBufferState zbuff = new ZBufferState(); + zbuff.setEnabled(false); + setRenderState(zbuff); + + final FogState fs = new FogState(); + fs.setEnabled(false); + setRenderState(fs); + + final CullState cs = new CullState(); + cs.setCullFace(Face.Front); + setRenderState(cs); + + if (updateBuffers) { + setGeometryData(outsideView, center); + setIndexData(); + } + } + + /** + * Generates the vertices of the dome + * + * @param outsideView + * If the dome should be viewed from the outside (if not zbuffer is used) + * @param center + */ + private void setGeometryData(final boolean outsideView, final Vector3 center) { + final Vector3 tempVa = Vector3.fetchTempInstance(); + final Vector3 tempVb = Vector3.fetchTempInstance(); + final Vector3 tempVc = Vector3.fetchTempInstance(); + + // allocate vertices, we need one extra in each radial to get the + // correct texture coordinates + final int verts = (_planes - 1) * (_radialSamples + 1) + 1; + _meshData.setVertexBuffer(BufferUtils.createVector3Buffer(verts)); + + // allocate normals + _meshData.setColorBuffer(BufferUtils.createColorBuffer(verts)); + + // generate geometry + final double fInvRS = 1.0 / _radialSamples; + final double fYFactor = 1.0 / (_planes - 1); + + // Generate points on the unit circle to be used in computing the mesh + // points on a dome slice. + final double[] afSin = new double[_radialSamples]; + final double[] afCos = new double[_radialSamples]; + for (int iR = 0; iR < _radialSamples; iR++) { + final double fAngle = MathUtils.TWO_PI * fInvRS * iR; + afCos[iR] = MathUtils.cos(fAngle); + afSin[iR] = MathUtils.sin(fAngle); + } + + // generate the dome itself + int i = 0; + for (int iY = 0; iY < _planes - 1; iY++) { + final double fYFraction = fYFactor * iY; // in (0,1) + final double fY = _radius * fYFraction; + // compute center of slice + final Vector3 kSliceCenter = tempVb.set(center); + kSliceCenter.addLocal(0, fY, 0); + + // compute radius of slice + final double fSliceRadius = Math.sqrt(Math.abs(_radius * _radius - fY * fY)); + + // compute slice vertices + final int iSave = i; + for (int iR = 0; iR < _radialSamples; iR++) { + final Vector3 kRadial = tempVc.set(afCos[iR], 0, afSin[iR]); + kRadial.multiply(fSliceRadius, tempVa); + _meshData.getVertexBuffer().put((float) (kSliceCenter.getX() + tempVa.getX())) + .put((float) (kSliceCenter.getY() + tempVa.getY())) + .put((float) (kSliceCenter.getZ() + tempVa.getZ())); + + BufferUtils.populateFromBuffer(tempVa, _meshData.getVertexBuffer(), i); + + ColorRGBA.lerp(midColor, topColor, (float) Math.sqrt(fYFraction), tmpColor); + _meshData.getColorBuffer().put(tmpColor.getRed()).put(tmpColor.getGreen()).put(tmpColor.getBlue()) + .put(1); + + i++; + } + + BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iSave, i); + BufferUtils.copyInternalColor(_meshData.getColorBuffer(), iSave, i); + + i++; + } + + // pole + _meshData.getVertexBuffer().put((float) center.getX()).put((float) (center.getY() + _radius)) + .put((float) center.getZ()); + + _meshData.getColorBuffer().put(topColor.getRed()).put(topColor.getGreen()).put(topColor.getBlue()).put(1); + + Vector3.releaseTempInstance(tempVa); + Vector3.releaseTempInstance(tempVb); + Vector3.releaseTempInstance(tempVc); + } + + public void updateColors() { + final Vector3 tempVa = Vector3.fetchTempInstance(); + final Vector3 tempVb = Vector3.fetchTempInstance(); + final Vector3 tempVc = Vector3.fetchTempInstance(); + + final double fYFactor = 1.0 / (_planes - 1); + + _meshData.getColorBuffer().clear(); + + // generate the dome itself + int i = 0; + for (int iY = 0; iY < _planes - 1; iY++) { + final double fYFraction = fYFactor * iY; // in (0,1) + + // compute slice vertices + final int iSave = i; + for (int iR = 0; iR < _radialSamples; iR++) { + ColorRGBA.lerp(midColor, topColor, (float) Math.sqrt(fYFraction), tmpColor); + _meshData.getColorBuffer().put(tmpColor.getRed()).put(tmpColor.getGreen()).put(tmpColor.getBlue()) + .put(1); + i++; + } + + BufferUtils.copyInternalVector3(_meshData.getVertexBuffer(), iSave, i); + BufferUtils.copyInternalColor(_meshData.getColorBuffer(), iSave, i); + + i++; + } + + _meshData.getColorBuffer().put(topColor.getRed()).put(topColor.getGreen()).put(topColor.getBlue()).put(1); + + Vector3.releaseTempInstance(tempVa); + Vector3.releaseTempInstance(tempVb); + Vector3.releaseTempInstance(tempVc); + } + + /** + * Generates the connections + */ + private void setIndexData() { + // allocate connectivity + final int verts = (_planes - 1) * (_radialSamples + 1) + 1; + final int tris = (_planes - 2) * _radialSamples * 2 + _radialSamples; + _meshData.setIndices(BufferUtils.createIndexBufferData(3 * tris, verts - 1)); + + // Generate only for middle planes + for (int plane = 1; plane < _planes - 1; plane++) { + final int bottomPlaneStart = (plane - 1) * (_radialSamples + 1); + final int topPlaneStart = plane * (_radialSamples + 1); + for (int sample = 0; sample < _radialSamples; sample++) { + _meshData.getIndices().put(bottomPlaneStart + sample); + _meshData.getIndices().put(topPlaneStart + sample); + _meshData.getIndices().put(bottomPlaneStart + sample + 1); + _meshData.getIndices().put(bottomPlaneStart + sample + 1); + _meshData.getIndices().put(topPlaneStart + sample); + _meshData.getIndices().put(topPlaneStart + sample + 1); + } + } + + // pole triangles + final int bottomPlaneStart = (_planes - 2) * (_radialSamples + 1); + for (int samples = 0; samples < _radialSamples; samples++) { + _meshData.getIndices().put(bottomPlaneStart + samples); + _meshData.getIndices().put(_meshData.getVertexCount() - 1); + _meshData.getIndices().put(bottomPlaneStart + samples + 1); + } + } + + public int getPlanes() { + return _planes; + } + + public int getRadialSamples() { + return _radialSamples; + } + + public double getRadius() { + return _radius; + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_planes, "planes", 0); + capsule.write(_radialSamples, "radialSamples", 0); + capsule.write(_radius, "radius", 0); + + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _planes = capsule.readInt("planes", 0); + _radialSamples = capsule.readInt("radialSamples", 0); + _radius = capsule.readDouble("radius", 0); + + } + + public ColorRGBA getMidColor() { + return midColor; + } + + public ColorRGBA getTopColor() { + return topColor; + } +}
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/objects/Skybox.java b/ardor3d-craft/src/main/java/com/ardorcraft/objects/Skybox.java new file mode 100644 index 0000000..528cef5 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/objects/Skybox.java @@ -0,0 +1,257 @@ +/** + * 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.ardorcraft.objects; + +import java.io.IOException; + +import com.ardor3d.image.Texture; +import com.ardor3d.image.Texture.WrapMode; +import com.ardor3d.math.Matrix3; +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.FogState; +import com.ardor3d.renderer.state.RenderState; +import com.ardor3d.renderer.state.RenderState.StateType; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.renderer.state.ZBufferState; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.hint.CullHint; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.scenegraph.hint.TextureCombineMode; +import com.ardor3d.scenegraph.shape.Quad; +import com.ardor3d.util.export.CapsuleUtils; +import com.ardor3d.util.export.InputCapsule; +import com.ardor3d.util.export.OutputCapsule; + +/** + * A Box made of textured quads that simulate having a sky, horizon and so forth around your scene. Either attach to a + * camera node or update on each frame to set this skybox at the camera's position. + */ +public class Skybox extends Node { + + public enum Face { + /** The +Z side of the skybox. */ + North, + /** The -Z side of the skybox. */ + South, + /** The -X side of the skybox. */ + East, + /** The +X side of the skybox. */ + West, + /** The +Y side of the skybox. */ + Up, + /** The -Y side of the skybox. */ + Down; + } + + private float _xExtent; + private float _yExtent; + private float _zExtent; + + private Quad[] _skyboxQuads; + + public Skybox() {} + + /** + * Creates a new skybox. The size of the skybox and name is specified here. By default, no textures are set. + * + * @param name + * The name of the skybox. + * @param xExtent + * The x size of the skybox in both directions from the center. + * @param yExtent + * The y size of the skybox in both directions from the center. + * @param zExtent + * The z size of the skybox in both directions from the center. + */ + public Skybox(final String name, final float xExtent, final float yExtent, final float zExtent) { + super(name); + + _xExtent = xExtent; + _yExtent = yExtent; + _zExtent = zExtent; + + initialize(); + } + + /** + * Set the texture to be displayed on the given face of the skybox. Replaces any existing texture on that face. + * + * @param face + * the face to set + * @param texture + * The texture for that side to assume. + * @throws IllegalArgumentException + * if face is null. + */ + public void setTexture(final Face face, final Texture texture) { + if (face == null) { + throw new IllegalArgumentException("Face can not be null."); + } + + _skyboxQuads[face.ordinal()].clearRenderState(RenderState.StateType.Texture); + setTexture(face, texture, 0); + } + + /** + * Set the texture to be displayed on the given side of the skybox. Only replaces the texture at the index specified + * by textureUnit. + * + * @param face + * the face to set + * @param texture + * The texture for that side to assume. + * @param textureUnit + * The texture unite of the given side's TextureState the texture will assume. + */ + public void setTexture(final Face face, final Texture texture, final int textureUnit) { + // Validate + if (face == null) { + throw new IllegalArgumentException("Face can not be null."); + } + + TextureState ts = (TextureState) _skyboxQuads[face.ordinal()] + .getLocalRenderState(RenderState.StateType.Texture); + if (ts == null) { + ts = new TextureState(); + } + + // Initialize the texture state + ts.setTexture(texture, textureUnit); + ts.setEnabled(true); + + texture.setWrap(WrapMode.EdgeClamp); + + // Set the texture to the quad + _skyboxQuads[face.ordinal()].setRenderState(ts); + + return; + } + + public Texture getTexture(final Face face) { + if (face == null) { + throw new IllegalArgumentException("Face can not be null."); + } + return ((TextureState) _skyboxQuads[face.ordinal()].getLocalRenderState(RenderState.StateType.Texture)) + .getTexture(); + } + + public void initialize() { + + // Skybox consists of 6 sides + _skyboxQuads = new Quad[6]; + + // Create each of the quads + _skyboxQuads[Face.North.ordinal()] = new Quad("north", _xExtent * 2, _yExtent * 2); + _skyboxQuads[Face.North.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(180), 0)); + _skyboxQuads[Face.North.ordinal()].setTranslation(new Vector3(0, 0, _zExtent)); + _skyboxQuads[Face.South.ordinal()] = new Quad("south", _xExtent * 2, _yExtent * 2); + _skyboxQuads[Face.South.ordinal()].setTranslation(new Vector3(0, 0, -_zExtent)); + _skyboxQuads[Face.East.ordinal()] = new Quad("east", _zExtent * 2, _yExtent * 2); + _skyboxQuads[Face.East.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(90), 0)); + _skyboxQuads[Face.East.ordinal()].setTranslation(new Vector3(-_xExtent, 0, 0)); + _skyboxQuads[Face.West.ordinal()] = new Quad("west", _zExtent * 2, _yExtent * 2); + _skyboxQuads[Face.West.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(270), 0)); + _skyboxQuads[Face.West.ordinal()].setTranslation(new Vector3(_xExtent, 0, 0)); + _skyboxQuads[Face.Up.ordinal()] = new Quad("up", _xExtent * 2, _zExtent * 2); + _skyboxQuads[Face.Up.ordinal()] + .setRotation(new Matrix3().fromAngles(Math.toRadians(90), Math.toRadians(270), 0)); + _skyboxQuads[Face.Up.ordinal()].setTranslation(new Vector3(0, _yExtent, 0)); + _skyboxQuads[Face.Down.ordinal()] = new Quad("down", _xExtent * 2, _zExtent * 2); + _skyboxQuads[Face.Down.ordinal()].setRotation(new Matrix3().fromAngles(Math.toRadians(270), + Math.toRadians(270), 0)); + _skyboxQuads[Face.Down.ordinal()].setTranslation(new Vector3(0, -_yExtent, 0)); + + // We don't want the light to effect our skybox + getSceneHints().setLightCombineMode(LightCombineMode.Off); + + getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + + final ZBufferState zbuff = new ZBufferState(); + zbuff.setEnabled(false); + setRenderState(zbuff); + + final FogState fs = new FogState(); + fs.setEnabled(false); + setRenderState(fs); + + // We don't want it making our skybox disapear, so force view + getSceneHints().setCullHint(CullHint.Never); + + for (int i = 0; i < 6; i++) { + // Make sure texture is only what is set. + _skyboxQuads[i].getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); + + // Make sure no lighting on the skybox + _skyboxQuads[i].getSceneHints().setLightCombineMode(LightCombineMode.Off); + + // Make sure the quad is viewable + _skyboxQuads[i].getSceneHints().setCullHint(CullHint.Never); + + // Add to the prebucket. + _skyboxQuads[i].getSceneHints().setRenderBucketType(RenderBucketType.Skip); + + // And attach the skybox as a child + attachChild(_skyboxQuads[i]); + } + } + + /** + * Retrieve the quad indicated by the given side. + * + * @param face + * One of Skybox.Face.North, Skybox.Face.South, and so on... + * @return The Quad that makes up that side of the Skybox. + */ + public Quad getFace(final Face face) { + return _skyboxQuads[face.ordinal()]; + } + + public void preloadTexture(final Face face, final Renderer r) { + final TextureState ts = (TextureState) _skyboxQuads[face.ordinal()] + .getLocalRenderState(RenderState.StateType.Texture); + if (ts != null) { + r.applyState(StateType.Texture, ts); + } + } + + /** + * Force all of the textures to load. This prevents pauses later during the application as you pan around the world. + */ + public void preloadTextures(final Renderer r) { + for (int x = 0; x < 6; x++) { + final TextureState ts = (TextureState) _skyboxQuads[x].getLocalRenderState(RenderState.StateType.Texture); + if (ts != null) { + r.applyState(StateType.Texture, ts); + } + } + + } + + @Override + public void write(final OutputCapsule capsule) throws IOException { + super.write(capsule); + capsule.write(_xExtent, "xExtent", 0); + capsule.write(_yExtent, "yExtent", 0); + capsule.write(_zExtent, "zExtent", 0); + capsule.write(_skyboxQuads, "skyboxQuads", null); + } + + @Override + public void read(final InputCapsule capsule) throws IOException { + super.read(capsule); + _xExtent = capsule.readFloat("xExtent", 0); + _yExtent = capsule.readFloat("yExtent", 0); + _zExtent = capsule.readFloat("zExtent", 0); + _skyboxQuads = CapsuleUtils.asArray(capsule.readSavableArray("skyboxQuads", null), Quad.class); + } +}
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/AStarHeuristic.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/AStarHeuristic.java new file mode 100644 index 0000000..70babce --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/AStarHeuristic.java @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.pathfinding; + + +/** + * Use to define your own heuristic for use with the {@link ConstrainedAStar}. + */ +public interface AStarHeuristic { + public float getCost(int x, int y, int z, int tx, int ty, int tz); +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/ConstrainedAStar.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/ConstrainedAStar.java new file mode 100644 index 0000000..8cf8584 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/ConstrainedAStar.java @@ -0,0 +1,180 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.pathfinding; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Set; + +import com.ardorcraft.pathfinding.calculator.DefaultAStarCalculator; +import com.ardorcraft.pathfinding.heuristics.ClosestHeuristic; +import com.ardorcraft.world.BlockProvider; + +/** + * AStar pathfinding with pluggable cost calculator (since that is game specific). + * <p> + * Typical usage: + * <p> + * + * <pre> + * ConstrainedAStar pathFinder = new ConstrainedAStar(blockWorld, 40); + * ... + * PathResult pathResult = pathFinder.findPath(source.x, source.y, source.z, target.x, target.y, target.z); + * </pre> + */ +public class ConstrainedAStar { + private final Set<PathNode> closed = new HashSet<>(); + private final PriorityQueue<PathNode> open = new PriorityQueue<>(); + private final Map<PathNode, PathNode> nodeTable = new HashMap<>(); + + private final int maxSearchDistance; + private final AStarHeuristic heuristic; + private final MoveCalculator moveCalculator; + private final BlockProvider map; + private final float heuristicMultiplier = 0.2f; + + public ConstrainedAStar(final BlockProvider map, final int maxSearchDistance) { + this(map, maxSearchDistance, new ClosestHeuristic(), new DefaultAStarCalculator()); + } + + public ConstrainedAStar(final BlockProvider map, final int maxSearchDistance, final AStarHeuristic heuristic, + final MoveCalculator moveCalculator) { + this.heuristic = heuristic; + this.map = map; + this.maxSearchDistance = maxSearchDistance; + this.moveCalculator = moveCalculator; + } + + public PathResult findPath(final int sx, final int sy, final int sz, final int tx, final int ty, final int tz) { + closed.clear(); + open.clear(); + nodeTable.clear(); + final MoveData moveData = new MoveData(); + + final PathNode startNode = new PathNode(sx, sy, sz); + final PathNode endNode = new PathNode(tx, ty, tz); + + if (startNode.equals(endNode)) { + return new PathResult(null, true); + } + + startNode.heuristic = getHeuristicCost(startNode, tx, ty, tz) * heuristicMultiplier; + open.add(startNode); + nodeTable.put(startNode, startNode); + nodeTable.put(endNode, endNode); + + PathNode current = null; + int maxDepth = 0; + while (maxDepth < maxSearchDistance && !open.isEmpty()) { + current = open.poll(); + if (current == null || current.equals(endNode)) { + break; + } + + Thread.yield(); + + closed.add(current); + + for (int x = -1; x < 2; x++) { + for (int z = -1; z < 2; z++) { + if (x == 0 && z == 0) { + continue; + } + + moveCalculator.calculateMove(map, moveData, current, x, z); + if (moveData.canMove) { + final float nextStepCost = current.cost + moveData.cost; + + PathNode neighbour = new PathNode(current.x + x, moveData.newY, current.z + z); + if (nodeTable.containsKey(neighbour)) { + neighbour = nodeTable.get(neighbour); + } else { + nodeTable.put(neighbour, neighbour); + } + + if (nextStepCost < neighbour.cost) { + if (open.contains(neighbour)) { + open.remove(neighbour); + } + if (closed.contains(neighbour)) { + closed.remove(neighbour); + } + } + + if (!open.contains(neighbour) && !closed.contains(neighbour)) { + neighbour.cost = nextStepCost; + neighbour.heuristic = getHeuristicCost(neighbour, tx, ty, tz) * heuristicMultiplier; + maxDepth = Math.max(maxDepth, neighbour.setParent(current)); + open.add(neighbour); + } + } + } + } + } + + if (current == null) { + return new PathResult(null, false); + } + if (!current.equals(endNode)) { + PathNode minNode = null; + float minHeuristic = Float.MAX_VALUE; + for (final PathNode node : nodeTable.keySet()) { + if (node != endNode && node.heuristic + node.cost < minHeuristic) { + minHeuristic = node.heuristic + node.cost; + minNode = node; + } + } + + if (minNode == null) { + return new PathResult(null, false); + } + if (minNode.equals(startNode)) { + final List<PathNode> result = new ArrayList<>(); + result.add(minNode); + return new PathResult(result, false); + } + + final List<PathNode> result = new ArrayList<>(); + PathNode target = minNode; + while (target != null && target != startNode) { + result.add(target); + target = target.parent; + } + Collections.reverse(result); + + closed.clear(); + open.clear(); + nodeTable.clear(); + + return new PathResult(result, false); + } + + final List<PathNode> result = new ArrayList<>(); + PathNode target = endNode; + while (target != null && target != startNode) { + result.add(target); + target = target.parent; + } + Collections.reverse(result); + + closed.clear(); + open.clear(); + nodeTable.clear(); + + return new PathResult(result, true); + } + + private float getHeuristicCost(final PathNode node, final int tx, final int ty, final int tz) { + return heuristic.getCost(node.getX(), node.getY(), node.getZ(), tx, ty, tz); + } + +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveCalculator.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveCalculator.java new file mode 100644 index 0000000..1ee4e64 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveCalculator.java @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.pathfinding; + +import com.ardorcraft.world.BlockProvider; + +public interface MoveCalculator { + void calculateMove(BlockProvider map, final MoveData moveData, final PathNode current, final int xChange, + final int zChange); +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveData.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveData.java new file mode 100644 index 0000000..070f8c0 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/MoveData.java @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.pathfinding; + +public class MoveData { + MoveData() { + + } + + public boolean canMove = false; + public float cost = 0; + public int newY = 0; +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathNode.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathNode.java new file mode 100644 index 0000000..1537dfe --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathNode.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.pathfinding; + +public class PathNode implements Comparable<PathNode> { + public final int x; + public final int y; + public final int z; + + PathNode parent; + + float cost; + float heuristic; + int depth; + + public PathNode(final int x, final int y, final int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public int setParent(final PathNode parent) { + depth = parent.depth + 1; + this.parent = parent; + + return depth; + } + + @Override + public int compareTo(final PathNode other) { + final PathNode o = other; + + final float f = heuristic + cost; + final float of = o.heuristic + o.cost; + + if (f < of) { + return -1; + } else if (f > of) { + return 1; + } else { + return 0; + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + x; + result = prime * result + y; + result = prime * result + z; + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof PathNode)) { + return false; + } + final PathNode other = (PathNode) obj; + if (x != other.x) { + return false; + } + if (y != other.y) { + return false; + } + if (z != other.z) { + return false; + } + return true; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + public float getCost() { + return cost; + } + + @Override + public String toString() { + return "PathNode [x=" + x + ", y=" + y + ", z=" + z + ", cost=" + cost + ", heuristic=" + heuristic + "]"; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathResult.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathResult.java new file mode 100644 index 0000000..e72e729 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/PathResult.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.pathfinding; + +import java.util.List; + +public class PathResult { + private final List<PathNode> path; + private final boolean isFullPath; + + public PathResult(final List<PathNode> path, final boolean isFullPath) { + this.path = path; + this.isFullPath = isFullPath; + } + + public List<PathNode> getPath() { + return path; + } + + public boolean isFullPath() { + return isFullPath; + } + + @Override + public String toString() { + return "PathResult [path=" + path + ", isFullPath=" + isFullPath + "]"; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/calculator/DefaultAStarCalculator.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/calculator/DefaultAStarCalculator.java new file mode 100644 index 0000000..7af7815 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/calculator/DefaultAStarCalculator.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.pathfinding.calculator; + +import com.ardorcraft.pathfinding.MoveCalculator; +import com.ardorcraft.pathfinding.MoveData; +import com.ardorcraft.pathfinding.PathNode; +import com.ardorcraft.world.BlockProvider; + +public class DefaultAStarCalculator implements MoveCalculator { + @Override + public void calculateMove(final BlockProvider map, final MoveData moveData, final PathNode current, + final int xChange, final int zChange) { + moveData.canMove = false; + moveData.cost = 0.01f; + moveData.newY = current.y; + + // Special handling of diagonal movement + if (xChange != 0 && zChange != 0) { + final int block1 = map.getBlock(current.x + xChange, current.y, current.z); + final int block2 = map.getBlock(current.x + xChange, current.y + 1, current.z); + + final int block3 = map.getBlock(current.x, current.y, current.z + zChange); + final int block4 = map.getBlock(current.x, current.y + 1, current.z + zChange); + + final int block5 = map.getBlock(current.x + xChange, current.y, current.z + zChange); + final int block6 = map.getBlock(current.x + xChange, current.y + 1, current.z + zChange); + + if (block1 == 0 && block2 == 0 && block3 == 0 && block4 == 0 && block5 == 0 && block6 == 0) { + moveData.canMove = true; + moveData.cost = 0.05f * 1.42f; + + for (int y = moveData.newY - 1; y >= 0; y--) { + final int block7 = map.getBlock(current.x + xChange, y, current.z + zChange); + if (block7 != 0) { + moveData.newY = y + 1; + final int height = Math.abs(moveData.newY - current.y); + moveData.cost = 0.05f * 1.42f + 0.05f * height; + if (height > 3) { + moveData.cost *= 10.0f; + } + if (height > 5) { + moveData.canMove = false; + } + break; + } + } + } + return; + } + + final int block1 = map.getBlock(current.x + xChange, current.y, current.z + zChange); + final int block2 = map.getBlock(current.x + xChange, current.y + 1, current.z + zChange); + // Walk straight on + if (block1 == 0 && block2 == 0) { + moveData.canMove = true; + + for (int y = moveData.newY - 1; y >= 0; y--) { + final int block3 = map.getBlock(current.x + xChange, y, current.z + zChange); + if (block3 != 0) { + moveData.newY = y + 1; + final int height = Math.abs(moveData.newY - current.y); + moveData.cost = 0.05f + 0.05f * height; + if (height > 3) { + moveData.cost *= 10.0f; + } + if (height > 5) { + moveData.canMove = false; + } + break; + } + } + } + // Climb + else if (block1 != 0 && block2 == 0) { + final int block3 = map.getBlock(current.x, current.y + 2, current.z); + final int block4 = map.getBlock(current.x + xChange, current.y + 2, current.z + zChange); + if (block3 == 0 && block4 == 0) { + moveData.canMove = true; + moveData.cost = 0.1f; + moveData.newY += 1; + } + } + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestHeuristic.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestHeuristic.java new file mode 100644 index 0000000..d37bdb0 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestHeuristic.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.pathfinding.heuristics; + +import com.ardorcraft.pathfinding.AStarHeuristic; + +public class ClosestHeuristic implements AStarHeuristic { + @Override + public float getCost(final int x, final int y, final int z, final int tx, final int ty, final int tz) { + final float dx = tx - x; + final float dy = ty - y; + final float dz = tz - z; + + final float result = (float) Math.sqrt(dx * dx + dy * dy + dz * dz); + + return result; + } + +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestSquaredHeuristic.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestSquaredHeuristic.java new file mode 100644 index 0000000..7d7c1f6 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ClosestSquaredHeuristic.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.pathfinding.heuristics; + +import com.ardorcraft.pathfinding.AStarHeuristic; + +public class ClosestSquaredHeuristic implements AStarHeuristic { + + @Override + public float getCost(final int x, final int y, final int z, final int tx, final int ty, final int tz) { + final float dx = tx - x; + final float dy = ty - y; + final float dz = tz - z; + + return dx * dx + dy * dy + dz * dz; + } + +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ManhattanHeuristic.java b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ManhattanHeuristic.java new file mode 100644 index 0000000..4846b52 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/pathfinding/heuristics/ManhattanHeuristic.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.pathfinding.heuristics; + +import com.ardorcraft.pathfinding.AStarHeuristic; + +public class ManhattanHeuristic implements AStarHeuristic { + private final int minimumCost; + + public ManhattanHeuristic(final int minimumCost) { + this.minimumCost = minimumCost; + } + + @Override + public float getCost(final int x, final int y, final int z, final int tx, final int ty, final int tz) { + return minimumCost * (Math.abs(x - tx) + Math.abs(y - ty) + Math.abs(z - tz)); + } + +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/player/PlayerBase.java b/ardor3d-craft/src/main/java/com/ardorcraft/player/PlayerBase.java new file mode 100644 index 0000000..8e98c1f --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/player/PlayerBase.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.player; + +import com.ardor3d.math.Vector3; + +/** + * Useful baseclass for player handling + */ +public class PlayerBase { + protected final Vector3 position; + protected final Vector3 direction; + protected final Vector3 up; + protected final Vector3 left; + protected final Vector3 velocity; + protected final Vector3 acceleration; + + public PlayerBase() { + position = new Vector3(0, 0, 0); + direction = new Vector3(Vector3.UNIT_Z); + up = new Vector3(Vector3.UNIT_Y); + left = new Vector3(Vector3.UNIT_X); + velocity = new Vector3(); + acceleration = new Vector3(); + } + + public Vector3 getPosition() { + return position; + } + + public Vector3 getDirection() { + return direction; + } + + public Vector3 getUp() { + return up; + } + + public Vector3 getVelocity() { + return velocity; + } + + public Vector3 getLeft() { + return left; + } + + public Vector3 getAcceleration() { + return acceleration; + } + + public void normalize() { + left.normalizeLocal(); + up.normalizeLocal(); + direction.normalizeLocal(); + } + +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/BlockUtil.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/BlockUtil.java new file mode 100644 index 0000000..9baf07c --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/BlockUtil.java @@ -0,0 +1,497 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.Vector2; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector2; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.shape.Quad; +import com.ardor3d.util.geom.MeshCombiner; +import com.ardorcraft.util.geometryproducers.BoxProducer; +import com.ardorcraft.util.geometryproducers.GeometryProducer; +import com.ardorcraft.util.geometryproducers.MeshProducer; +import com.ardorcraft.world.BlockSide; +import com.ardorcraft.world.BlockType; +import com.ardorcraft.world.BlockWorld; + +/** + * Main class to use for setting up properties for all block types. + */ +public final class BlockUtil { + private static final Logger logger = Logger.getLogger(BlockUtil.class.getName()); + + private final int totalTextureSizeWidth; + private final int totalTextureSizeHeight; + private final int textureSize; + private final float textureReciprocalX; + private final float textureReciprocalY; + private final float offsetWidth; + private final float offsetHeight; + private final int xCount; + private final int yCount; + + private final Map<Integer, EnumMap<BlockSide, Vector2>> blockTextureCoords = new HashMap<>(); + private final BlockType[] types = new BlockType[256]; + private final boolean[] isLocalLightMap = new boolean[256]; + private final GeometryProducer[] geometryProducers = new GeometryProducer[256]; + private final boolean[] isCollidable = new boolean[256]; + private final boolean[] isPickable = new boolean[256]; + private final boolean[] isSemiTransparent = new boolean[256]; + + public BlockUtil(final int textureWidth, final int textureHeight, final int subTextureSize) { + totalTextureSizeWidth = textureWidth; + totalTextureSizeHeight = textureHeight; + textureSize = subTextureSize; + + textureReciprocalX = (float) textureSize / (float) totalTextureSizeWidth; + textureReciprocalY = (float) textureSize / (float) totalTextureSizeHeight; + + xCount = totalTextureSizeWidth / textureSize; + yCount = totalTextureSizeHeight / textureSize; + + offsetWidth = 0.1f / totalTextureSizeWidth; + offsetHeight = 0.1f / totalTextureSizeHeight; + + setupDefaultBlocks(); + } + + private void setupDefaultBlocks() { + final GeometryProducer producer = new BoxProducer(); + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + final int index = y * 16 + x + 1; + if (index > 255) { + break; + } + setBlockMapping(index, x, y); + setBlockType(index, BlockType.Solid); + setGeometryProducer(index, producer); + setIsCollidable(index, true); + setIsPickable(index, true); + setIsLocalLightSource(index, false); + } + } + + final Mesh vegetationMesh = createVegetationMesh(); + final MeshProducer vegetationMeshProducer = new MeshProducer(vegetationMesh); + vegetationMeshProducer.setTransformTextureCoords(true); + + // setBlockType(BlockWorld.WATER, BlockType.Transparent); + setIsSemiTransparent(BlockWorld.WATER, true); + setIsPickable(BlockWorld.WATER, false); + setIsCollidable(BlockWorld.WATER, false); + + setBlockMapping(1, 1, 0); // stone + setBlockMapping(2, 0, 0, 3, 0, 2, 0); // grass + setBlockMapping(3, 2, 0); // dirt + setBlockMapping(4, 0, 1); // cobblestone + setBlockMapping(5, 4, 0); // wooden plank + + setBlockMapping(6, 15, 0); // sapling + setBlockType(6, BlockType.Transparent); + setGeometryProducer(6, vegetationMeshProducer); + setIsCollidable(6, false); + + setBlockMapping(7, 1, 1); // bedrock + setBlockMapping(8, 13, 12); // water + setBlockMapping(9, 13, 12); // stationary water + setBlockMapping(10, 13, 14); // lava + setBlockMapping(11, 13, 14); // stationary lava + setBlockMapping(12, 2, 1); // sand + setBlockMapping(13, 3, 1); // gravel + setBlockMapping(14, 0, 2); // gold ore + setBlockMapping(15, 1, 2); // iron ore + setBlockMapping(16, 2, 2); // coal ore + setBlockMapping(17, 4, 1); // wood + + setBlockMapping(18, 4, 3); // leaves + setBlockType(18, BlockType.Transparent); + + setBlockMapping(19, 0, 3); // sponge + + setBlockMapping(20, 1, 3); // glass + setBlockType(20, BlockType.Transparent); + + setBlockMapping(21, 0, 10); // lapis ore + setBlockMapping(22, 0, 9); // lapis block + setBlockMapping(23, 14, 3, 13, 2, 14, 3); // dispenser + setBlockMapping(24, 0, 12); // sandstone + + setBlockMapping(37, 13, 0); // dandelion + setBlockType(37, BlockType.Transparent); + setGeometryProducer(37, vegetationMeshProducer); + setIsCollidable(37, false); + setBlockMapping(38, 12, 0); // rose + setBlockType(38, BlockType.Transparent); + setGeometryProducer(38, vegetationMeshProducer); + setIsCollidable(38, false); + setBlockMapping(39, 12, 1); // brown mushroom + setBlockType(39, BlockType.Transparent); + setGeometryProducer(39, vegetationMeshProducer); + setIsCollidable(39, false); + setBlockMapping(40, 13, 1); // red mushroom + setBlockType(40, BlockType.Transparent); + setGeometryProducer(40, vegetationMeshProducer); + setIsCollidable(40, false); + + setBlockMapping(41, 7, 1); // gold block + setBlockMapping(42, 6, 1); // iron block + setBlockMapping(43, 5, 0); // double slabs + setBlockMapping(44, 5, 0); // slabs + setBlockMapping(45, 7, 0); // brick block + setBlockMapping(46, 8, 0); // tnt + setBlockMapping(47, 4, 0, 3, 2, 4, 0, 4, 0, 4, 0, 4, 0); // bookshelf + setBlockMapping(48, 4, 2); // moss stone + setBlockMapping(49, 5, 2); // obsidian + + setBlockMapping(50, 0, 5); // torch + setBlockType(50, BlockType.Transparent); + setGeometryProducer(50, vegetationMeshProducer); + setIsCollidable(50, false); + setIsLocalLightSource(50, true); + + setBlockMapping(51, 0, 5); // fire + setBlockType(51, BlockType.Transparent); + setIsCollidable(51, false); + setIsLocalLightSource(51, true); + + setBlockMapping(52, 1, 4); // monster spawner + setBlockType(52, BlockType.Transparent); + + setBlockMapping(53, 4, 0); // stairs + setBlockMapping(54, 9, 1, 10, 1, 11, 1); // chest + + setBlockMapping(55, 4, 10); // redstone wire + setBlockType(55, BlockType.Transparent); + + setBlockMapping(56, 2, 3); // diamond ore + setBlockMapping(57, 8, 1); // diamond block + setBlockMapping(58, 11, 2, 11, 3, 11, 2); // crafting table + + setBlockMapping(59, 15, 5); // seeds + setBlockType(59, BlockType.Transparent); + + setBlockMapping(60, 7, 5); // farmland + setBlockMapping(61, 12, 2); // furnace + setBlockMapping(62, 13, 3); // burning furnace + setBlockMapping(63, 4, 0); // sign post + + setBlockMapping(64, 1, 5); // door + setBlockType(64, BlockType.Transparent); + setBlockMapping(65, 3, 5); // ladders + setBlockType(65, BlockType.Transparent); + setBlockMapping(66, 0, 8); // rails + setBlockType(66, BlockType.Transparent); + + setBlockMapping(67, 0, 1); // cobblestone stairs + setBlockMapping(68, 4, 0); // wallsign + setBlockMapping(69, 4, 8); // lever + setBlockMapping(70, 6, 0); // stone pressure plate + setBlockMapping(71, 2, 5); // iron door + setBlockMapping(72, 4, 0); // wooden pressure plate + setBlockMapping(73, 3, 3); // redstone ore + setBlockMapping(74, 3, 3); // glowing redstone ore + + setBlockMapping(75, 0, 6); // redstone torch off + setBlockType(75, BlockType.Transparent); + + setBlockMapping(76, 0, 5); // restone torch on + setBlockType(76, BlockType.Transparent); + setIsLocalLightSource(76, true); + + setBlockMapping(77, 0, 1); // stone button + setBlockMapping(78, 2, 4); // snow + setBlockMapping(79, 2, 4); // ice + setBlockMapping(80, 2, 4); // snow block + + setBlockMapping(81, 5, 4, 6, 4, 5, 4); // cactus + setBlockType(81, BlockType.Transparent); + + setBlockMapping(82, 3, 1); // clay block + + setBlockMapping(83, 9, 4); // sugar cane + setBlockType(83, BlockType.Transparent); + + setBlockMapping(84, 11, 4, 10, 4, 10, 4); // jukebox + + setBlockMapping(85, 5, 9); // fence + setBlockType(85, BlockType.Transparent); + + setBlockMapping(86, 6, 6, 7, 7, 6, 7); // pumpkin + setBlockMapping(87, 7, 6); // netherrack + setBlockMapping(88, 8, 6); // soul sand + setBlockMapping(89, 9, 6); // glowstone block + setBlockMapping(90, 1, 12); // portal + setBlockMapping(91, 1, 12); // jack-o-lantern + + setBlockMapping(92, 9, 7, 10, 7, 12, 7); // cake block + setBlockType(92, BlockType.Transparent); + + setBlockMapping(93, 7, 6); // redstone repeater off + setBlockMapping(94, 7, 6); // redstone repeater on + setBlockMapping(95, 9, 1, 11, 1, 10, 1, 10, 1, 10, 1, 9, 1); // locked chest + + // Some grass stuff + for (int i = 0; i < 8; i++) { + setBlockType(100 + i, BlockType.Transparent); + setBlockMapping(100 + i, 8 + i, 5); + setIsCollidable(100 + i, false); + setIsPickable(100 + i, false); + setGeometryProducer(100 + i, vegetationMeshProducer); + } + } + + /** + * Set if this block should emit light + * + * @param blockId + * @param value + */ + public void setIsLocalLightSource(final int blockId, final boolean value) { + isLocalLightMap[blockId] = value; + } + + /** + * Setup texture tiles to use for this block + * + * @param blockId + * @param x + * @param y + */ + public void setBlockMapping(final int blockId, final int x, final int y) { + setBlockMapping(blockId, x, y, x, y, x, y); + } + + /** + * Setup texture tiles to use for this block + * + * @param blockId + * @param xTop + * @param yTop + * @param xSide + * @param ySide + * @param xBottom + * @param yBottom + */ + public void setBlockMapping(final int blockId, final int xTop, final int yTop, final int xSide, final int ySide, + final int xBottom, final int yBottom) { + setBlockMapping(blockId, xTop, yTop, xSide, ySide, xSide, ySide, xSide, ySide, xSide, ySide, xBottom, yBottom); + } + + /** + * Setup texture tiles to use for this block + * + * @param blockId + * @param xTop + * @param yTop + * @param xFront + * @param yFront + * @param xBack + * @param yBack + * @param xLeft + * @param yLeft + * @param xRight + * @param yRight + * @param xBottom + * @param yBottom + */ + public void setBlockMapping(final int blockId, final int xTop, final int yTop, final int xFront, final int yFront, + final int xBack, final int yBack, final int xLeft, final int yLeft, final int xRight, final int yRight, + final int xBottom, final int yBottom) { + setBlockMapping(blockId, BlockSide.Top, xTop, yTop); + setBlockMapping(blockId, BlockSide.Front, xFront, yFront); + setBlockMapping(blockId, BlockSide.Back, xBack, yBack); + setBlockMapping(blockId, BlockSide.Left, xLeft, yLeft); + setBlockMapping(blockId, BlockSide.Right, xRight, yRight); + setBlockMapping(blockId, BlockSide.Bottom, xBottom, yBottom); + } + + /** + * Setup texture tiles to use for this block + * + * @param blockId + * @param side + * @param x + * @param y + */ + public void setBlockMapping(final int blockId, final BlockSide side, final int x, final int y) { + EnumMap<BlockSide, Vector2> coord = blockTextureCoords.get(blockId); + if (coord == null) { + coord = new EnumMap<>(BlockSide.class); + blockTextureCoords.put(blockId, coord); + } + fillCoord(coord, side, (yCount - y - 1) * xCount + x); + } + + /** + * Set if this block is see-through or not (if not, then hidden surface removal can be done) + * + * @param blockId + * @param type + */ + public void setBlockType(final int blockId, final BlockType type) { + types[blockId] = type; + } + + public BlockType getBlockType(final int blockId) { + return types[blockId]; + } + + public boolean isLocalLight(final int block) { + return isLocalLightMap[block]; + } + + private void fillCoord(final EnumMap<BlockSide, Vector2> coord, final BlockSide blockSide, final int pos) { + final double x = pos % xCount * textureReciprocalX; + final double y = pos / yCount * textureReciprocalY; + + coord.put(blockSide, new Vector2(x, y)); + } + + /** + * Get start coordinates for a specific side of a block. + * + * @param blockId + * @param blockSide + * @return + */ + public ReadOnlyVector2 getBlockTextureCoord(final int blockId, final BlockSide blockSide) { + if (!blockTextureCoords.containsKey(blockId)) { + logger.info("No mapping found for blockId: " + blockId); + return Vector2.ZERO; + } + return blockTextureCoords.get(blockId).get(blockSide); + } + + /** + * Get offset to fix the artifact issue of texture bleeding. + * + * @return + */ + public float getOffsetWidth() { + return offsetWidth; + } + + /** + * Get offset to fix the artifact issue of texture bleeding. + * + * @return + */ + public float getOffsetHeight() { + return offsetHeight; + } + + /** + * Get the width of each tile in the texture atlas. + * + * @return + */ + public float getTileWidth() { + return textureReciprocalX; + } + + /** + * Get the height of each tile in the texture atlas. + * + * @return + */ + public float getTileHeight() { + return textureReciprocalY; + } + + /** + * Set the geometry producer to use for this block id. + * + * @param blockId + * @param handler + */ + public void setGeometryProducer(final int blockId, final GeometryProducer handler) { + geometryProducers[blockId] = handler; + } + + public GeometryProducer getGeometryProducer(final int blockId) { + return geometryProducers[blockId]; + } + + /** + * Set if the block should be collidable. + * + * @param blockId + * @param value + */ + public void setIsCollidable(final int blockId, final boolean value) { + isCollidable[blockId] = value; + } + + public boolean getIsCollidable(final int blockId) { + return isCollidable[blockId]; + } + + /** + * Set if the block should be pickable. + * + * @param blockId + * @param value + */ + public void setIsPickable(final int blockId, final boolean value) { + isPickable[blockId] = value; + } + + public boolean getIsPickable(final int blockId) { + return isPickable[blockId]; + } + + /** + * Set if block is semi-transparent like water and should not generate sides when next to itself. + * + * @param blockId + * @param value + */ + public void setIsSemiTransparent(final int blockId, final boolean value) { + isSemiTransparent[blockId] = value; + } + + public boolean getIsSemiTransparent(final int blockId) { + return isSemiTransparent[blockId]; + } + + private Mesh createVegetationMesh() { + final Node combineNode = new Node(); + + final double sizeX = 1; + final double sizeY = 1; + + Quad q = new Quad("quad", sizeX, sizeY); + q.setRotation(new Quaternion().fromAngleAxis(MathUtils.HALF_PI * 0.5, Vector3.UNIT_Y)); + combineNode.attachChild(q); + + q = new Quad("quad", sizeX, sizeY); + q.setRotation(new Quaternion().fromAngleAxis(-MathUtils.HALF_PI * 0.5, Vector3.UNIT_Y)); + combineNode.attachChild(q); + + q = new Quad("quad", sizeX, sizeY); + q.setRotation(new Quaternion().fromAngleAxis(MathUtils.HALF_PI * 1.5, Vector3.UNIT_Y)); + combineNode.attachChild(q); + + q = new Quad("quad", sizeX, sizeY); + q.setRotation(new Quaternion().fromAngleAxis(-MathUtils.HALF_PI * 1.5, Vector3.UNIT_Y)); + combineNode.attachChild(q); + + combineNode.updateWorldTransform(true); + + return MeshCombiner.combine(combineNode); + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/ColorUtil.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/ColorUtil.java new file mode 100644 index 0000000..b73c050 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/ColorUtil.java @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util; + +import java.awt.Color; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.util.List; + +/** + * Utility class for tinting areas of an image (for example tint black/white grass patch for biomes) + */ +public class ColorUtil { + public static BufferedImage tintAreas(final BufferedImage input, final List<Rectangle> areas, + final int sourceTintX, final int sourceTintY) throws Exception { + BufferedImage image = input; + switch (input.getType()) { + case BufferedImage.TYPE_INT_ARGB: + case BufferedImage.TYPE_INT_RGB: + break; + case BufferedImage.TYPE_4BYTE_ABGR: + image = new BufferedImage(input.getWidth(), input.getHeight(), BufferedImage.TYPE_INT_ARGB); + image.getGraphics().drawImage(input, 0, 0, null); + break; + default: + throw new Exception("Input terrain texture, unhandled image data format: " + input.getType()); + } + + boolean alpha = false; + final int col = image.getRGB(sourceTintX, sourceTintY); + final int red = col >> 16 & 0xff; + final int green = col >> 8 & 0xff; + final int blue = col & 0xff; + final float[] hsb = Color.RGBtoHSB(red, green, blue, null); + final float hue = hsb[0]; + final float saturation = hsb[1];// * 0.9f; + + switch (image.getType()) { + case BufferedImage.TYPE_INT_ARGB: + alpha = true; + // Falls through on purpose. + case BufferedImage.TYPE_INT_RGB: + for (final Rectangle area : areas) { + tintArea(image, area, alpha, hue, saturation); + } + break; + default: + throw new Exception("Tinting texture, unhandled image data format: " + image.getType()); + } + return image; + } + + private static void tintArea(final BufferedImage input, final Rectangle area, final boolean alpha, final float hue, + final float saturation) { + for (int x = area.x; x < area.x + area.width; x++) { + for (int y = area.y; y < area.y + area.height; y++) { + final int col = input.getRGB(x, y); + + // Specify 3 RGB values + final int red = col >> 16 & 0xff; + final int green = col >> 8 & 0xff; + final int blue = col & 0xff; + + final float[] hsb = Color.RGBtoHSB(red, green, blue, null); + final float brightness = hsb[2]; + + int argb = Color.HSBtoRGB(hue, saturation, brightness) & 0xffffff; + + // add alpha, if applicable + if (alpha) { + argb |= col & 0xff000000; + } + + // apply to image + input.setRGB(x, y, argb); + } + } + } +}
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedList.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedList.java new file mode 100644 index 0000000..2704807 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedList.java @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Utility class used by the mailbox update system. + * + * @param <T> + */ +public final class DoubleBufferedList<T> { + private List<T> frontList = new ArrayList<>(); + private List<T> backList = new ArrayList<>(); + + /** + * The add method can be called at any point. + * + * @param t + */ + public void add(final T t) { + if (t != null) { + synchronized (backList) { + backList.add(t); + } + } + } + + /** + * The switchAndGet call and it's returned list has to be accessed sequencially. + * + * @return The list + */ + public List<T> switchAndGet() { + if (backList.isEmpty()) { + return Collections.emptyList(); + } + synchronized (backList) { + final List<T> tmp = backList; + backList = frontList; + frontList = tmp; + backList.clear(); + return frontList; + } + } + + public boolean isEmpty() { + return backList.isEmpty(); + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedSet.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedSet.java new file mode 100644 index 0000000..64315b9 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/DoubleBufferedSet.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Utility class used by the mailbox update system. + * + * @param <T> + */ +public class DoubleBufferedSet<T> { + private Set<T> frontSet = new HashSet<>(); + private Set<T> backSet = new HashSet<>(); + + /** + * The add method can be called at any point. + * + * @param t + */ + public void add(final T t) { + synchronized (backSet) { + backSet.add(t); + } + } + + /** + * The switchAndGet call and it's returned list has to be accessed sequencially. + * + * @return The Set + */ + public Set<T> switchAndGet() { + if (backSet.isEmpty()) { + return Collections.emptySet(); + } + synchronized (backSet) { + final Set<T> tmp = backSet; + backSet = frontSet; + frontSet = tmp; + backSet.clear(); + return frontSet; + } + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/FontHandler.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/FontHandler.java new file mode 100644 index 0000000..2339166 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/FontHandler.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util; + +import java.io.IOException; + +import com.ardor3d.image.Texture.MagnificationFilter; +import com.ardor3d.image.Texture.MinificationFilter; +import com.ardor3d.renderer.queue.RenderBucketType; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.ui.text.BMFont; +import com.ardor3d.ui.text.BMText.Align; +import com.ardor3d.ui.text.BasicText; +import com.ardor3d.util.resource.ResourceLocatorTool; +import com.ardor3d.util.resource.URLResourceSource; + +public class FontHandler { + public static BMFont MINI_FONT; + public static int MINI_FONT_SIZE = 8; + public static BMFont BIG_FONT; + public static int BIG_FONT_SIZE = 15; + public static BMFont SMALL_FONT; + public static int SMALL_FONT_SIZE = 12; + public static BMFont MEDIUM_FONT; + public static int MEDIUM_FONT_SIZE = 16; + + static { + try { + MINI_FONT = new BMFont(new URLResourceSource(ResourceLocatorTool.getClassPathResource(FontHandler.class, + "com/ardorcraft/resources/mini.fnt")), true); + MINI_FONT.getPageTexture().setMinificationFilter(MinificationFilter.NearestNeighborNoMipMaps); + MINI_FONT.getPageTexture().setMagnificationFilter(MagnificationFilter.NearestNeighbor); + BIG_FONT = new BMFont(new URLResourceSource(ResourceLocatorTool.getClassPathResource(FontHandler.class, + "com/ardorcraft/resources/big.fnt")), true); + BIG_FONT.getPageTexture().setMinificationFilter(MinificationFilter.NearestNeighborNoMipMaps); + BIG_FONT.getPageTexture().setMagnificationFilter(MagnificationFilter.NearestNeighbor); + SMALL_FONT = new BMFont(new URLResourceSource(ResourceLocatorTool.getClassPathResource(FontHandler.class, + "com/ardor3d/extension/ui/font/arial-12-regular.fnt")), true); + MEDIUM_FONT = new BMFont(new URLResourceSource(ResourceLocatorTool.getClassPathResource(FontHandler.class, + "com/ardor3d/extension/ui/font/arial-16-bold-regular.fnt")), true); + } catch (final IOException e) { + e.printStackTrace(); + } + } + + private static BlendState bs = new BlendState(); + static { + bs.setBlendEnabled(true); + } + + public static BasicText createText(final String text) { + return createText(text, FontHandler.SMALL_FONT, FontHandler.SMALL_FONT_SIZE); + } + + public static BasicText createText(final String text, final BMFont font, final int size) { + final BasicText itemLabel = new BasicText("Test", text, font, size); + itemLabel.getSceneHints().setRenderBucketType(RenderBucketType.Skip); + itemLabel.setAlign(Align.East); + itemLabel.setRenderState(bs); + itemLabel.updateGeometricState(0, true); + return itemLabel; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/ImprovedNoise.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/ImprovedNoise.java new file mode 100644 index 0000000..c2345fe --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/ImprovedNoise.java @@ -0,0 +1,197 @@ +/** + * 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.ardorcraft.util; + +import com.ardor3d.math.MathUtils; + +/** + * <code>ImprovedNoise</code> Fast perlin noise. + * + * @author Ken Perlin + */ +public final class ImprovedNoise { + public static double noise(double x, double y, double z) { + final int X = (int) MathUtils.floor(x) & 255, // FIND UNIT CUBE THAT + Y = (int) MathUtils.floor(y) & 255, // CONTAINS POINT. + Z = (int) MathUtils.floor(z) & 255; + x -= MathUtils.floor(x); // FIND RELATIVE X,Y,Z + y -= MathUtils.floor(y); // OF POINT IN CUBE. + z -= MathUtils.floor(z); + final double u = fade(x), // COMPUTE FADE CURVES + v = fade(y), // FOR EACH OF X,Y,Z. + w = fade(z); + final int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z, // HASH COORDINATES OF + B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; // THE 8 CUBE CORNERS, + + return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), // AND ADD + grad(p[BA], x - 1, y, z)), // BLENDED + lerp(u, grad(p[AB], x, y - 1, z), // RESULTS + grad(p[BB], x - 1, y - 1, z))),// FROM 8 + lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), // CORNERS + grad(p[BA + 1], x - 1, y, z - 1)), // OF CUBE + lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1)))); + } + + private static double fade(final double t) { + return t * t * t * (t * (t * 6 - 15) + 10); + } + + private static double lerp(final double t, final double a, final double b) { + return a + t * (b - a); + } + + private static double grad(final int hash, final double x, final double y, final double z) { + final int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE + final double u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS. + v = h < 4 ? y : h == 12 || h == 14 ? x : z; + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } + + private static final int p[] = new int[512], permutation[] = { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, + 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, + 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, + 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, + 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, + 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, + 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, + 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, + 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, + 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, + 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, + 180 + }; + + static { + for (int i = 0; i < 256; i++) { + p[256 + i] = p[i] = permutation[i]; + } + } + + public static double noise2d(final double x, final double y, final int nbOctave) { + int result = 0; + final int frequence256 = 256; + int sx = (int) (x * frequence256); + int sy = (int) (y * frequence256); + int octave = nbOctave; + while (octave != 0) { + final int bX = sx & 0xFF; + final int bY = sy & 0xFF; + + final int sxp = sx >> 8; + final int syp = sy >> 8; + + // Compute noise for each corner of current cell + final int Y1376312589_00 = syp * 1376312589; + final int Y1376312589_01 = Y1376312589_00 + 1376312589; + + final int XY1376312589_00 = sxp + Y1376312589_00; + final int XY1376312589_10 = XY1376312589_00 + 1; + final int XY1376312589_01 = sxp + Y1376312589_01; + final int XY1376312589_11 = XY1376312589_01 + 1; + + final int XYBASE_00 = XY1376312589_00 << 13 ^ XY1376312589_00; + final int XYBASE_10 = XY1376312589_10 << 13 ^ XY1376312589_10; + final int XYBASE_01 = XY1376312589_01 << 13 ^ XY1376312589_01; + final int XYBASE_11 = XY1376312589_11 << 13 ^ XY1376312589_11; + + int alt1 = XYBASE_00 * (XYBASE_00 * XYBASE_00 * 15731 + 789221) + 1376312589; + int alt2 = XYBASE_10 * (XYBASE_10 * XYBASE_10 * 15731 + 789221) + 1376312589; + int alt3 = XYBASE_01 * (XYBASE_01 * XYBASE_01 * 15731 + 789221) + 1376312589; + int alt4 = XYBASE_11 * (XYBASE_11 * XYBASE_11 * 15731 + 789221) + 1376312589; + + /* + * NOTE : on for true grandiant noise uncomment following block for true gradiant we need to perform scalar + * product here, gradiant vector are created/deducted using the above pseudo random values (alt1...alt4) : + * by cutting thoses values in twice values to get for each a fixed x,y vector gradX1= alt1&0xFF gradY1= + * (alt1&0xFF00)>>8 + * + * the last part of the PRN (alt1&0xFF0000)>>8 is used as an offset to correct one of the gradiant problem + * wich is zero on cell edge + * + * source vector (sXN;sYN) for scalar product are computed using (bX,bY) + * + * each four values must be replaced by the result of the following altN=(gradXN;gradYN) scalar (sXN;sYN) + * + * all the rest of the code (interpolation+accumulation) is identical for value & gradiant noise + */ + + /* START BLOCK FOR TRUE GRADIANT NOISE */ + + final int grad1X = (alt1 & 0xFF) - 128; + final int grad1Y = (alt1 >> 8 & 0xFF) - 128; + final int grad2X = (alt2 & 0xFF) - 128; + final int grad2Y = (alt2 >> 8 & 0xFF) - 128; + final int grad3X = (alt3 & 0xFF) - 128; + final int grad3Y = (alt3 >> 8 & 0xFF) - 128; + final int grad4X = (alt4 & 0xFF) - 128; + final int grad4Y = (alt4 >> 8 & 0xFF) - 128; + + final int sX1 = bX >> 1; + final int sY1 = bY >> 1; + final int sX2 = 128 - sX1; + final int sY2 = sY1; + final int sX3 = sX1; + final int sY3 = 128 - sY1; + final int sX4 = 128 - sX1; + final int sY4 = 128 - sY1; + alt1 = grad1X * sX1 + grad1Y * sY1 + 16384 + ((alt1 & 0xFF0000) >> 9); // to avoid seams to be 0 we use an + // offset + alt2 = grad2X * sX2 + grad2Y * sY2 + 16384 + ((alt2 & 0xFF0000) >> 9); + alt3 = grad3X * sX3 + grad3Y * sY3 + 16384 + ((alt3 & 0xFF0000) >> 9); + alt4 = grad4X * sX4 + grad4Y * sY4 + 16384 + ((alt4 & 0xFF0000) >> 9); + + /* END BLOCK FOR TRUE GRADIANT NOISE */ + + /* START BLOCK FOR VALUE NOISE */ + /* + * alt1&=0xFFFF; alt2&=0xFFFF; alt3&=0xFFFF; alt4&=0xFFFF; + */ + /* END BLOCK FOR VALUE NOISE */ + + /* START BLOCK FOR LINEAR INTERPOLATION */ + // BiLinear interpolation + /* + * int f24=(bX*bY)>>8; int f23=bX-f24; int f14=bY-f24; int f13=256-f14-f23-f24; + * + * int val=(alt1*f13+alt2*f23+alt3*f14+alt4*f24); + */ + /* END BLOCK FOR LINEAR INTERPOLATION */ + + // BiCubic interpolation ( in the form alt(bX) = alt[n] - (3*bX^2 - 2*bX^3) * (alt[n] - alt[n+1]) ) + /* START BLOCK FOR BICUBIC INTERPOLATION */ + final int bX2 = bX * bX >> 8; + final int bX3 = bX2 * bX >> 8; + final int _3bX2 = 3 * bX2; + final int _2bX3 = 2 * bX3; + final int alt12 = alt1 - ((_3bX2 - _2bX3) * (alt1 - alt2) >> 8); + final int alt34 = alt3 - ((_3bX2 - _2bX3) * (alt3 - alt4) >> 8); + + final int bY2 = bY * bY >> 8; + final int bY3 = bY2 * bY >> 8; + final int _3bY2 = 3 * bY2; + final int _2bY3 = 2 * bY3; + int val = alt12 - ((_3bY2 - _2bY3) * (alt12 - alt34) >> 8); + + val *= 256; + /* END BLOCK FOR BICUBIC INTERPOLATION */ + + // Accumulate in result + result += val << octave; + + octave--; + sx <<= 1; + sy <<= 1; + + } + return (result >>> 16 + nbOctave + 1) / 255.0 - 0.5; + } +}
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/MouseButtonHeldCondition.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/MouseButtonHeldCondition.java new file mode 100644 index 0000000..4df8f38 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/MouseButtonHeldCondition.java @@ -0,0 +1,54 @@ +/** + * 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.ardorcraft.util; + +import java.util.function.Predicate; + +import com.ardor3d.annotation.Immutable; +import com.ardor3d.input.ButtonState; +import com.ardor3d.input.InputState; +import com.ardor3d.input.MouseButton; +import com.ardor3d.input.logical.TwoInputStates; + +/** + * A condition that is true if a given button was pressed when going from the previous input state to the current one. + */ +@Immutable +public final class MouseButtonHeldCondition implements Predicate<TwoInputStates> { + private final MouseButton _button; + + /** + * Construct a new MouseButtonHeldCondition. + * + * @param button + * the button that should be pressed to trigger this condition + * @throws NullPointerException + * if the button is null + */ + public MouseButtonHeldCondition(final MouseButton button) { + if (button == null) { + throw new NullPointerException(); + } + + _button = button; + } + + @Override + public boolean test(final TwoInputStates states) { + final InputState currentState = states.getCurrent(); + + if (currentState == null || !currentState.getMouseState().hasButtonState(ButtonState.DOWN)) { + return false; + } + + return currentState.getMouseState().getButtonState(_button) == ButtonState.DOWN; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/SimplexNoise.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/SimplexNoise.java new file mode 100644 index 0000000..e3f2ee4 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/SimplexNoise.java @@ -0,0 +1,706 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util; + +/** + * Simplex Noise in 2D, 3D and 4D. Based on the example code of this paper: + * http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf + * + * @author Stefan Gustavson, Linkping University, Sweden (stegu at itn dot liu dot se) + * + * Slight optimizations & restructuring by + * @author Karsten Schmidt (info at toxi dot co dot uk) + * + */ + +public class SimplexNoise { + + private static final double SQRT3 = Math.sqrt(3.0); + private static final double SQRT5 = Math.sqrt(5.0); + + /** + * Skewing and unskewing factors for 2D, 3D and 4D, some of them pre-multiplied. + */ + private static final double F2 = 0.5 * (SQRT3 - 1.0); + private static final double G2 = (3.0 - SQRT3) / 6.0; + private static final double G22 = G2 * 2.0 - 1; + + private static final double F3 = 1.0 / 3.0; + private static final double G3 = 1.0 / 6.0; + + private static final double F4 = (SQRT5 - 1.0) / 4.0; + private static final double G4 = (5.0 - SQRT5) / 20.0; + private static final double G42 = G4 * 2.0; + private static final double G43 = G4 * 3.0; + private static final double G44 = G4 * 4.0 - 1.0; + + /** + * Gradient vectors for 3D (pointing to mid points of all edges of a unit cube) + */ + private static final int[][] grad3 = { + { + 1, 1, 0 + }, { + -1, 1, 0 + }, { + 1, -1, 0 + }, { + -1, -1, 0 + }, { + 1, 0, 1 + }, { + -1, 0, 1 + }, { + 1, 0, -1 + }, { + -1, 0, -1 + }, { + 0, 1, 1 + }, { + 0, -1, 1 + }, { + 0, 1, -1 + }, { + 0, -1, -1 + } + }; + + /** + * Gradient vectors for 4D (pointing to mid points of all edges of a unit 4D hypercube) + */ + private static final int[][] grad4 = { + { + 0, 1, 1, 1 + }, { + 0, 1, 1, -1 + }, { + 0, 1, -1, 1 + }, { + 0, 1, -1, -1 + }, { + 0, -1, 1, 1 + }, { + 0, -1, 1, -1 + }, { + 0, -1, -1, 1 + }, { + 0, -1, -1, -1 + }, { + 1, 0, 1, 1 + }, { + 1, 0, 1, -1 + }, { + 1, 0, -1, 1 + }, { + 1, 0, -1, -1 + }, { + -1, 0, 1, 1 + }, { + -1, 0, 1, -1 + }, { + -1, 0, -1, 1 + }, { + -1, 0, -1, -1 + }, { + 1, 1, 0, 1 + }, { + 1, 1, 0, -1 + }, { + 1, -1, 0, 1 + }, { + 1, -1, 0, -1 + }, { + -1, 1, 0, 1 + }, { + -1, 1, 0, -1 + }, { + -1, -1, 0, 1 + }, { + -1, -1, 0, -1 + }, { + 1, 1, 1, 0 + }, { + 1, 1, -1, 0 + }, { + 1, -1, 1, 0 + }, { + 1, -1, -1, 0 + }, { + -1, 1, 1, 0 + }, { + -1, 1, -1, 0 + }, { + -1, -1, 1, 0 + }, { + -1, -1, -1, 0 + } + }; + + /** + * Permutation table + */ + private static final int[] p = { + 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, + 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, + 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, + 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, + 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, + 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, + 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152, + 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, + 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, + 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, + 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, + 180 + }; + + /** + * To remove the need for index wrapping, double the permutation table length + */ + private static int[] perm = new int[0x200]; + /** + * A lookup table to traverse the simplex around a given point in 4D. Details can be found where this table is used, + * in the 4D noise method. + */ + private static final int[][] simplex = { + { + 0, 1, 2, 3 + }, { + 0, 1, 3, 2 + }, { + 0, 0, 0, 0 + }, { + 0, 2, 3, 1 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 1, 2, 3, 0 + }, { + 0, 2, 1, 3 + }, { + 0, 0, 0, 0 + }, { + 0, 3, 1, 2 + }, { + 0, 3, 2, 1 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 1, 3, 2, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 1, 2, 0, 3 + }, { + 0, 0, 0, 0 + }, { + 1, 3, 0, 2 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 2, 3, 0, 1 + }, { + 2, 3, 1, 0 + }, { + 1, 0, 2, 3 + }, { + 1, 0, 3, 2 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 2, 0, 3, 1 + }, { + 0, 0, 0, 0 + }, { + 2, 1, 3, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 2, 0, 1, 3 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 3, 0, 1, 2 + }, { + 3, 0, 2, 1 + }, { + 0, 0, 0, 0 + }, { + 3, 1, 2, 0 + }, { + 2, 1, 0, 3 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 0, 0, 0, 0 + }, { + 3, 1, 0, 2 + }, { + 0, 0, 0, 0 + }, { + 3, 2, 0, 1 + }, { + 3, 2, 1, 0 + } + }; + + static { + for (int i = 0; i < 0x200; i++) { + perm[i] = p[i & 0xff]; + } + } + + /** + * Computes dot product in 2D. + * + * @param g + * 2-vector (grid offset) + * @param x + * @param y + * @return dot product + */ + private static double dot(final int g[], final double x, final double y) { + return g[0] * x + g[1] * y; + } + + /** + * Computes dot product in 3D. + * + * @param g + * 3-vector (grid offset) + * @param x + * @param y + * @param z + * @return dot product + */ + private static double dot(final int g[], final double x, final double y, final double z) { + return g[0] * x + g[1] * y + g[2] * z; + } + + /** + * Computes dot product in 4D. + * + * @param g + * 4-vector (grid offset) + * @param x + * @param y + * @param z + * @param w + * @return dot product + */ + private static double dot(final int g[], final double x, final double y, final double z, final double w) { + return g[0] * x + g[1] * y + g[2] * z + g[3] * w; + } + + /** + * This method is a *lot* faster than using (int)Math.floor(x). + * + * @param x + * value to be floored + * @return + */ + private static final int fastfloor(final double x) { + return x >= 0 ? (int) x : (int) x - 1; + } + + /** + * Computes 2D Simplex Noise. + * + * @param x + * coordinate + * @param y + * coordinate + * @return noise value in range -1 ... +1. + */ + public static double noise(final double x, final double y) { + double n0 = 0, n1 = 0, n2 = 0; // Noise contributions from the three + // corners + // Skew the input space to determine which simplex cell we're in + final double s = (x + y) * F2; // Hairy factor for 2D + final int i = fastfloor(x + s); + final int j = fastfloor(y + s); + final double t = (i + j) * G2; + final double x0 = x - (i - t); // The x,y distances from the cell origin + final double y0 = y - (j - t); + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) + if (x0 > y0) { + i1 = 1; + j1 = 0; + } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { + i1 = 0; + j1 = 1; + } // upper triangle, YX order: (0,0)->(0,1)->(1,1) + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + final double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed + final double y1 = y0 - j1 + G2; + final double x2 = x0 + G22; // Offsets for last corner in (x,y) unskewed + final double y2 = y0 + G22; + // Work out the hashed gradient indices of the three simplex corners + final int ii = i & 0xff; + final int jj = j & 0xff; + // Calculate the contribution from the three corners + double t0 = 0.5 - x0 * x0 - y0 * y0; + if (t0 > 0) { + t0 *= t0; + final int gi0 = perm[ii + perm[jj]] % 12; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for + // 2D gradient + } + double t1 = 0.5 - x1 * x1 - y1 * y1; + if (t1 > 0) { + t1 *= t1; + final int gi1 = perm[ii + i1 + perm[jj + j1]] % 12; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1); + } + double t2 = 0.5 - x2 * x2 - y2 * y2; + if (t2 > 0) { + t2 *= t2; + final int gi2 = perm[ii + 1 + perm[jj + 1]] % 12; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70.0 * (n0 + n1 + n2); + } + + /** + * Computes 3D Simplex Noise. + * + * @param x + * coordinate + * @param y + * coordinate + * @param z + * coordinate + * @return noise value in range -1 ... +1 + */ + public static double noise(final double x, final double y, final double z) { + double n0 = 0, n1 = 0, n2 = 0, n3 = 0; + // Noise contributions from the + // four corners + // Skew the input space to determine which simplex cell we're in + // final double F3 = 1.0 / 3.0; + final double s = (x + y + z) * F3; // Very nice and simple skew factor + // for 3D + final int i = fastfloor(x + s); + final int j = fastfloor(y + s); + final int k = fastfloor(z + s); + // final double G3 = 1.0 / 6.0; // Very nice and simple unskew factor, + // too + final double t = (i + j + k) * G3; + final double x0 = x - (i - t); // The x,y,z distances from the cell origin + final double y0 = y - (j - t); + final double z0 = z - (k - t); + // For the 3D case, the simplex shape is a slightly irregular + // tetrahedron. + // Determine which simplex we are in. + int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) + // coords + int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } // X Y Z order + else if (x0 >= z0) { + i1 = 1; + j1 = 0; + k1 = 0; + i2 = 1; + j2 = 0; + k2 = 1; + } // X Z Y order + else { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 1; + j2 = 0; + k2 = 1; + } // Z X Y order + } else { // x0<y0 + if (y0 < z0) { + i1 = 0; + j1 = 0; + k1 = 1; + i2 = 0; + j2 = 1; + k2 = 1; + } // Z Y X order + else if (x0 < z0) { + i1 = 0; + j1 = 1; + k1 = 0; + i2 = 0; + j2 = 1; + k2 = 1; + } // Y Z X order + else { + i1 = 0; + j1 = 1; + k1 = 0; + i2 = 1; + j2 = 1; + k2 = 0; + } // Y X Z order + } + // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z), + // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), + // and + // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), + // where + // c = 1/6. + final double x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords + final double y1 = y0 - j1 + G3; + final double z1 = z0 - k1 + G3; + + final double x2 = x0 - i2 + F3; // Offsets for third corner in (x,y,z) + final double y2 = y0 - j2 + F3; + final double z2 = z0 - k2 + F3; + + final double x3 = x0 - 0.5; // Offsets for last corner in (x,y,z) + final double y3 = y0 - 0.5; + final double z3 = z0 - 0.5; + // Work out the hashed gradient indices of the four simplex corners + final int ii = i & 0xff; + final int jj = j & 0xff; + final int kk = k & 0xff; + + // Calculate the contribution from the four corners + double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0; + if (t0 > 0) { + t0 *= t0; + final int gi0 = perm[ii + perm[jj + perm[kk]]] % 12; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0, z0); + } + double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; + if (t1 > 0) { + t1 *= t1; + final int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1]]] % 12; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1, z1); + } + double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; + if (t2 > 0) { + t2 *= t2; + final int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2]]] % 12; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2, z2); + } + double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; + if (t3 > 0) { + t3 *= t3; + final int gi3 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1]]] % 12; + n3 = t3 * t3 * dot(grad3[gi3], x3, y3, z3); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to stay just inside [-1,1] + return 32.0 * (n0 + n1 + n2 + n3); + } + + /** + * Computes 4D Simplex Noise. + * + * @param x + * coordinate + * @param y + * coordinate + * @param z + * coordinate + * @param w + * coordinate + * @return noise value in range -1 ... +1 + */ + public static double noise(final double x, final double y, final double z, final double w) { + // The skewing and unskewing factors are hairy again for the 4D case + double n0 = 0, n1 = 0, n2 = 0, n3 = 0, n4 = 0; // Noise contributions + // from the five corners + // Skew the (x,y,z,w) space to determine which cell of 24 simplices + final double s = (x + y + z + w) * F4; // Factor for 4D skewing + final int i = fastfloor(x + s); + final int j = fastfloor(y + s); + final int k = fastfloor(z + s); + final int l = fastfloor(w + s); + final double t = (i + j + k + l) * G4; // Factor for 4D unskewing + final double x0 = x - (i - t); // The x,y,z,w distances from the cell origin + final double y0 = y - (j - t); + final double z0 = z - (k - t); + final double w0 = w - (l - t); + // For the 4D case, the simplex is a 4D shape I won't even try to + // describe. + // To find out which of the 24 possible simplices we're in, we need to + // determine the magnitude ordering of x0, y0, z0 and w0. + // The method below is a good way of finding the ordering of x,y,z,w and + // then find the correct traversal order for the simplex were in. + // First, six pair-wise comparisons are performed between each possible + // pair of the four coordinates, and the results are used to add up + // binary bits for an integer index. + int c = 0; + if (x0 > y0) { + c = 0x20; + } + if (x0 > z0) { + c |= 0x10; + } + if (y0 > z0) { + c |= 0x08; + } + if (x0 > w0) { + c |= 0x04; + } + if (y0 > w0) { + c |= 0x02; + } + if (z0 > w0) { + c |= 0x01; + } + int i1, j1, k1, l1; // The integer offsets for the second simplex corner + int i2, j2, k2, l2; // The integer offsets for the third simplex corner + int i3, j3, k3, l3; // The integer offsets for the fourth simplex corner + // simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some + // order. Many values of c will never occur, since e.g. x>y>z>w makes + // x<z, y<w and x<w impossible. Only the 24 indices which have non-zero + // entries make any sense. We use a thresholding to set the coordinates + // in turn from the largest magnitude. The number 3 in the "simplex" + // array is at the position of the largest coordinate. + final int[] sc = simplex[c]; + i1 = sc[0] >= 3 ? 1 : 0; + j1 = sc[1] >= 3 ? 1 : 0; + k1 = sc[2] >= 3 ? 1 : 0; + l1 = sc[3] >= 3 ? 1 : 0; + // The number 2 in the "simplex" array is at the second largest + // coordinate. + i2 = sc[0] >= 2 ? 1 : 0; + j2 = sc[1] >= 2 ? 1 : 0; + k2 = sc[2] >= 2 ? 1 : 0; + l2 = sc[3] >= 2 ? 1 : 0; + // The number 1 in the "simplex" array is at the second smallest + // coordinate. + i3 = sc[0] >= 1 ? 1 : 0; + j3 = sc[1] >= 1 ? 1 : 0; + k3 = sc[2] >= 1 ? 1 : 0; + l3 = sc[3] >= 1 ? 1 : 0; + // The fifth corner has all coordinate offsets = 1, so no need to look + // that up. + final double x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) + final double y1 = y0 - j1 + G4; + final double z1 = z0 - k1 + G4; + final double w1 = w0 - l1 + G4; + + final double x2 = x0 - i2 + G42; // Offsets for third corner in (x,y,z,w) + final double y2 = y0 - j2 + G42; + final double z2 = z0 - k2 + G42; + final double w2 = w0 - l2 + G42; + + final double x3 = x0 - i3 + G43; // Offsets for fourth corner in (x,y,z,w) + final double y3 = y0 - j3 + G43; + final double z3 = z0 - k3 + G43; + final double w3 = w0 - l3 + G43; + + final double x4 = x0 + G44; // Offsets for last corner in (x,y,z,w) + final double y4 = y0 + G44; + final double z4 = z0 + G44; + final double w4 = w0 + G44; + + // Work out the hashed gradient indices of the five simplex corners + final int ii = i & 0xff; + final int jj = j & 0xff; + final int kk = k & 0xff; + final int ll = l & 0xff; + + // Calculate the contribution from the five corners + double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; + if (t0 > 0) { + t0 *= t0; + final int gi0 = perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32; + n0 = t0 * t0 * dot(grad4[gi0], x0, y0, z0, w0); + } + double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; + if (t1 > 0) { + t1 *= t1; + final int gi1 = perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32; + n1 = t1 * t1 * dot(grad4[gi1], x1, y1, z1, w1); + } + double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; + if (t2 > 0) { + t2 *= t2; + final int gi2 = perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32; + n2 = t2 * t2 * dot(grad4[gi2], x2, y2, z2, w2); + } + double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; + if (t3 > 0) { + t3 *= t3; + final int gi3 = perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32; + n3 = t3 * t3 * dot(grad4[gi3], x3, y3, z3, w3); + } + double t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; + if (t4 > 0) { + t4 *= t4; + final int gi4 = perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32; + n4 = t4 * t4 * dot(grad4[gi4], x4, y4, z4, w4); + } + // Sum up and scale the result to cover the range [-1,1] + return 27.0 * (n0 + n1 + n2 + n3 + n4); + } +}
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/BoxProducer.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/BoxProducer.java new file mode 100644 index 0000000..1101594 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/BoxProducer.java @@ -0,0 +1,494 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util.geometryproducers; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.type.ReadOnlyVector2; +import com.ardorcraft.data.Direction; +import com.ardorcraft.util.BlockUtil; +import com.ardorcraft.world.BlockProvider; +import com.ardorcraft.world.BlockSide; +import com.ardorcraft.world.BlockType; +import com.ardorcraft.world.GeometryHandler; + +/** + * Main blockworld geometry producer. Produces standard 1x1x1 boxes with hidden surface removal. + */ +public final class BoxProducer implements GeometryProducer { + private final int[] checkDirs = new int[] {// + -1, 0, 0, // + 1, 0, 0, // + 0, -1, 0, // + 0, 1, 0, // + 0, 0, -1, // + 0, 0, 1, // + }; + private final boolean[] sides = new boolean[] {// + false, true,// + true, false,// + false, true + // + }; + private final Direction[] directions = new Direction[] {// + Direction.X, Direction.X, // + Direction.Y, Direction.Y, // + Direction.Z, Direction.Z, // + }; + + private final float MOD = 0.12f; + private final float creaseAmount = 0.3f; + + private final BlockSide[][] orientations = new BlockSide[][] { + { // Front + BlockSide.Front, BlockSide.Back, BlockSide.Left, BlockSide.Right, BlockSide.Top, BlockSide.Bottom + }, { // Back + BlockSide.Back, BlockSide.Front, BlockSide.Right, BlockSide.Left, BlockSide.Top, BlockSide.Bottom + }, { // Left + BlockSide.Right, BlockSide.Left, BlockSide.Back, BlockSide.Front, BlockSide.Top, BlockSide.Bottom + }, { // Right + BlockSide.Left, BlockSide.Right, BlockSide.Front, BlockSide.Back, BlockSide.Top, BlockSide.Bottom + }, { // Top + BlockSide.Front, BlockSide.Back, BlockSide.Left, BlockSide.Right, BlockSide.Top, BlockSide.Bottom + }, { // Bottom + BlockSide.Front, BlockSide.Back, BlockSide.Left, BlockSide.Right, BlockSide.Top, BlockSide.Bottom + } + }; + + public BoxProducer() {} + + @Override + public void generateBlock(final int blockId, final GeometryHandler geometryHandler, final BlockProvider provider, + final BlockUtil blockUtil, final int x, final int y, final int z) { + int startIndex = 0; + + final boolean isTransparent = blockUtil.getIsSemiTransparent(blockId); + final BlockSide orientation = BlockSide.values()[provider.getBlockExtra(x, y, z)]; + + for (int face = 0; face < 6; face++) { + final int xChange = checkDirs[face * 3 + 0]; + final int yChange = checkDirs[face * 3 + 1]; + final int zChange = checkDirs[face * 3 + 2]; + final boolean side = sides[face]; + final Direction direction = directions[face]; + + final int neighbourBlock = provider.getBlock(x + xChange, y + yChange, z + zChange); + final boolean neighbourSolid = blockUtil.getBlockType(neighbourBlock) == BlockType.Solid; + final boolean isNeighbourTransparent = blockUtil.getIsSemiTransparent(neighbourBlock); + + if (neighbourBlock == 0 || !neighbourSolid || !isTransparent && isNeighbourTransparent) { + createBlockSide(geometryHandler, provider, blockUtil, orientation, x + Math.max(0, xChange), + y + Math.max(0, yChange), z + Math.max(0, zChange), blockId, side, direction, startIndex); + startIndex++; + } + } + geometryHandler.setIndexCount(startIndex * 6); + geometryHandler.setVertexCount(startIndex * 4); + } + + private void createBlockSide(final GeometryHandler geometryHandler, final BlockProvider provider, + final BlockUtil blockUtil, final BlockSide orientation, final int x, final int y, final int z, + final int block, final boolean first, final Direction dir, final int startIndex) { + if (dir == Direction.X) { + if (geometryHandler.hasVertices()) { + geometryHandler.setVertex(startIndex * 4 + 0, x, y, z); + geometryHandler.setVertex(startIndex * 4 + 1, x, y + 1, z); + geometryHandler.setVertex(startIndex * 4 + 2, x, y + 1, z + 1); + geometryHandler.setVertex(startIndex * 4 + 3, x, y, z + 1); + } + + if (geometryHandler.hasTextureCoords()) { + if (first) { + final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block, + getSide(orientation, BlockSide.Left)); + + geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getOffsetHeight()); + } else { + final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block, + getSide(orientation, BlockSide.Right)); + + geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight()); + } + } + + if (geometryHandler.hasColors()) { + float sideModifier = 0.04f; + if (first) { + sideModifier = MOD; + } + + float globalLighting, localLighting, miniShade; + + miniShade = miniShadeX(provider, blockUtil, x, y, z, 0, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y, z) - sideModifier, 0f, 1f) + * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y, z) - sideModifier, 0f, 1f) + * miniShade; + geometryHandler.setColor(startIndex * 4 + 0, globalLighting, localLighting); + + miniShade = miniShadeX(provider, blockUtil, x, y, z, 1, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y + 1, z) - sideModifier, 0f, 1f) + * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y + 1, z) - sideModifier, 0f, 1f) + * miniShade; + geometryHandler.setColor(startIndex * 4 + 1, globalLighting, localLighting); + + miniShade = miniShadeX(provider, blockUtil, x, y, z, 2, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y + 1, z + 1) - sideModifier, 0f, + 1f) * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y + 1, z + 1) - sideModifier, 0f, + 1f) * miniShade; + geometryHandler.setColor(startIndex * 4 + 2, globalLighting, localLighting); + + miniShade = miniShadeX(provider, blockUtil, x, y, z, 3, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y, z + 1) - sideModifier, 0f, 1f) + * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y, z + 1) - sideModifier, 0f, 1f) + * miniShade; + geometryHandler.setColor(startIndex * 4 + 3, globalLighting, localLighting); + } + + if (geometryHandler.hasIndices()) { + if (first) { + geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 0); + geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 3); + geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 2); + geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3); + } else { + geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 3); + geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 2); + geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 0); + geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3); + } + } + } else if (dir == Direction.Y) { + if (geometryHandler.hasVertices()) { + geometryHandler.setVertex(startIndex * 4 + 0, x, y, z); + geometryHandler.setVertex(startIndex * 4 + 1, x + 1, y, z); + geometryHandler.setVertex(startIndex * 4 + 2, x + 1, y, z + 1); + geometryHandler.setVertex(startIndex * 4 + 3, x, y, z + 1); + } + + if (geometryHandler.hasTextureCoords()) { + if (first) { + final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block, + getSide(orientation, BlockSide.Bottom)); + + geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getOffsetHeight()); + } else { + final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block, + getSide(orientation, BlockSide.Top)); + + geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight()); + } + } + + if (geometryHandler.hasColors()) { + float sideModifier = 0.0f; + if (first) { + sideModifier = MOD; + } + + float globalLighting, localLighting, miniShade; + + miniShade = miniShadeY(provider, blockUtil, x, y, z, 0, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y, z) - sideModifier, 0f, 1f) + * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y, z) - sideModifier, 0f, 1f) + * miniShade; + geometryHandler.setColor(startIndex * 4 + 0, globalLighting, localLighting); + + miniShade = miniShadeY(provider, blockUtil, x, y, z, 1, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x + 1, y, z) - sideModifier, 0f, 1f) + * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x + 1, y, z) - sideModifier, 0f, 1f) + * miniShade; + geometryHandler.setColor(startIndex * 4 + 1, globalLighting, localLighting); + + miniShade = miniShadeY(provider, blockUtil, x, y, z, 2, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x + 1, y, z + 1) - sideModifier, 0f, + 1f) * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x + 1, y, z + 1) - sideModifier, 0f, + 1f) * miniShade; + geometryHandler.setColor(startIndex * 4 + 2, globalLighting, localLighting); + + miniShade = miniShadeY(provider, blockUtil, x, y, z, 3, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y, z + 1) - sideModifier, 0f, 1f) + * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y, z + 1) - sideModifier, 0f, 1f) + * miniShade; + geometryHandler.setColor(startIndex * 4 + 3, globalLighting, localLighting); + } + + if (geometryHandler.hasIndices()) { + if (first) { + geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 0); + geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 3); + geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 2); + geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3); + } else { + geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 3); + geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 2); + geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 0); + geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3); + } + } + } else if (dir == Direction.Z) { + if (geometryHandler.hasVertices()) { + geometryHandler.setVertex(startIndex * 4 + 0, x, y, z); + geometryHandler.setVertex(startIndex * 4 + 1, x + 1, y, z); + geometryHandler.setVertex(startIndex * 4 + 2, x + 1, y + 1, z); + geometryHandler.setVertex(startIndex * 4 + 3, x, y + 1, z); + } + + if (geometryHandler.hasTextureCoords()) { + if (first) { + final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block, + getSide(orientation, BlockSide.Front)); + + geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + } else { + final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(block, + getSide(orientation, BlockSide.Back)); + + geometryHandler.setTextureCoord(startIndex * 4 + 0, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), coord.getYf() + blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 1, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 2, coord.getXf() + blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + geometryHandler.setTextureCoord(startIndex * 4 + 3, coord.getXf() + blockUtil.getTileWidth() + - blockUtil.getOffsetWidth(), + coord.getYf() + blockUtil.getTileHeight() - blockUtil.getOffsetHeight()); + } + } + + if (geometryHandler.hasColors()) { + float sideModifier = 0.04f; + if (first) { + sideModifier = MOD; + } + + float globalLighting, localLighting, miniShade; + + miniShade = miniShadeZ(provider, blockUtil, x, y, z, 0, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y, z) - sideModifier, 0f, 1f) + * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y, z) - sideModifier, 0f, 1f) + * miniShade; + geometryHandler.setColor(startIndex * 4 + 0, globalLighting, localLighting); + + miniShade = miniShadeZ(provider, blockUtil, x, y, z, 1, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x + 1, y, z) - sideModifier, 0f, 1f) + * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x + 1, y, z) - sideModifier, 0f, 1f) + * miniShade; + geometryHandler.setColor(startIndex * 4 + 1, globalLighting, localLighting); + + miniShade = miniShadeZ(provider, blockUtil, x, y, z, 2, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x + 1, y + 1, z) - sideModifier, 0f, + 1f) * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x + 1, y + 1, z) - sideModifier, 0f, + 1f) * miniShade; + geometryHandler.setColor(startIndex * 4 + 2, globalLighting, localLighting); + + miniShade = miniShadeZ(provider, blockUtil, x, y, z, 3, first); + globalLighting = MathUtils.clamp(geometryHandler.getGlobalLighting(x, y + 1, z) - sideModifier, 0f, 1f) + * miniShade; + localLighting = MathUtils.clamp(geometryHandler.getLocalLighting(x, y + 1, z) - sideModifier, 0f, 1f) + * miniShade; + geometryHandler.setColor(startIndex * 4 + 3, globalLighting, localLighting); + } + + if (geometryHandler.hasIndices()) { + if (first) { + geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 0); + geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 3); + geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 2); + geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3); + } else { + geometryHandler.setIndex(startIndex * 6 + 0, startIndex * 4 + 3); + geometryHandler.setIndex(startIndex * 6 + 1, startIndex * 4 + 2); + geometryHandler.setIndex(startIndex * 6 + 2, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 3, startIndex * 4 + 1); + geometryHandler.setIndex(startIndex * 6 + 4, startIndex * 4 + 0); + geometryHandler.setIndex(startIndex * 6 + 5, startIndex * 4 + 3); + } + } + } + } + + private BlockSide getSide(final BlockSide orientation, final BlockSide side) { + return orientations[orientation.ordinal()][side.ordinal()]; + } + + private float miniShadeX(final BlockProvider provider, final BlockUtil blockUtil, final int x, final int y, + final int z, final int index, final boolean first) { + final int xx = first ? x + 0 : x - 1; + final int yy = y; + final int zz = z; + + boolean b1, b2, b3; + + if (index == 0) { + b1 = isSolid(provider, blockUtil, xx, yy - 1, zz); + b2 = isSolid(provider, blockUtil, xx, yy, zz - 1); + b3 = isSolid(provider, blockUtil, xx, yy - 1, zz - 1); + return getShade(b1, b2, b3); + } else if (index == 1) { + b1 = isSolid(provider, blockUtil, xx, yy + 1, zz); + b2 = isSolid(provider, blockUtil, xx, yy, zz - 1); + b3 = isSolid(provider, blockUtil, xx, yy + 1, zz - 1); + return getShade(b1, b2, b3); + } else if (index == 2) { + b1 = isSolid(provider, blockUtil, xx, yy + 1, zz); + b2 = isSolid(provider, blockUtil, xx, yy, zz + 1); + b3 = isSolid(provider, blockUtil, xx, yy + 1, zz + 1); + return getShade(b1, b2, b3); + } else if (index == 3) { + b1 = isSolid(provider, blockUtil, xx, yy - 1, zz); + b2 = isSolid(provider, blockUtil, xx, yy, zz + 1); + b3 = isSolid(provider, blockUtil, xx, yy - 1, zz + 1); + return getShade(b1, b2, b3); + } + + return 1f; + } + + private float miniShadeY(final BlockProvider provider, final BlockUtil blockUtil, final int x, final int y, + final int z, final int index, final boolean first) { + final int xx = x; + final int yy = first ? y - 1 : y + 0; + final int zz = z; + + boolean b1, b2, b3; + + if (index == 0) { + b1 = isSolid(provider, blockUtil, xx - 1, yy, zz); + b2 = isSolid(provider, blockUtil, xx, yy, zz - 1); + b3 = isSolid(provider, blockUtil, xx - 1, yy, zz - 1); + return getShade(b1, b2, b3); + } else if (index == 1) { + b1 = isSolid(provider, blockUtil, xx + 1, yy, zz); + b2 = isSolid(provider, blockUtil, xx, yy, zz - 1); + b3 = isSolid(provider, blockUtil, xx + 1, yy, zz - 1); + return getShade(b1, b2, b3); + } else if (index == 2) { + b1 = isSolid(provider, blockUtil, xx + 1, yy, zz); + b2 = isSolid(provider, blockUtil, xx, yy, zz + 1); + b3 = isSolid(provider, blockUtil, xx + 1, yy, zz + 1); + return getShade(b1, b2, b3); + } else if (index == 3) { + b1 = isSolid(provider, blockUtil, xx - 1, yy, zz); + b2 = isSolid(provider, blockUtil, xx, yy, zz + 1); + b3 = isSolid(provider, blockUtil, xx - 1, yy, zz + 1); + return getShade(b1, b2, b3); + } + + return 1f; + } + + private float miniShadeZ(final BlockProvider provider, final BlockUtil blockUtil, final int x, final int y, + final int z, final int index, final boolean first) { + final int xx = x; + final int yy = y; + final int zz = first ? z + 0 : z - 1; + + boolean b1, b2, b3; + + if (index == 0) { + b1 = isSolid(provider, blockUtil, xx, yy - 1, zz); + b2 = isSolid(provider, blockUtil, xx - 1, yy, zz); + b3 = isSolid(provider, blockUtil, xx - 1, yy - 1, zz); + return getShade(b1, b2, b3); + } else if (index == 1) { + b1 = isSolid(provider, blockUtil, xx, yy - 1, zz); + b2 = isSolid(provider, blockUtil, xx + 1, yy, zz); + b3 = isSolid(provider, blockUtil, xx + 1, yy - 1, zz); + return getShade(b1, b2, b3); + } else if (index == 2) { + b1 = isSolid(provider, blockUtil, xx, yy + 1, zz); + b2 = isSolid(provider, blockUtil, xx + 1, yy, zz); + b3 = isSolid(provider, blockUtil, xx + 1, yy + 1, zz); + return getShade(b1, b2, b3); + } else if (index == 3) { + b1 = isSolid(provider, blockUtil, xx, yy + 1, zz); + b2 = isSolid(provider, blockUtil, xx - 1, yy, zz); + b3 = isSolid(provider, blockUtil, xx - 1, yy + 1, zz); + return getShade(b1, b2, b3); + } + + return 1f; + } + + // TODO: add solid amount + private boolean isSolid(final BlockProvider provider, final BlockUtil util, final int x, final int y, final int z) { + final int blockId = provider.getBlock(x, y, z); + if (blockId == 0) { + return false; + } + return util.getBlockType(blockId) == BlockType.Solid; + } + + private float getShade(final boolean b1, final boolean b2, final boolean b3) { + if (b1 && b2) { + return 1f - creaseAmount * 2.0f; + } else if (b1 || b2 || b3) { + return 1f - creaseAmount; + } + return 1f; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/GeometryProducer.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/GeometryProducer.java new file mode 100644 index 0000000..5941ce8 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/GeometryProducer.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util.geometryproducers; + +import com.ardorcraft.util.BlockUtil; +import com.ardorcraft.world.BlockProvider; +import com.ardorcraft.world.GeometryHandler; + +/** + * Pluggable interface to create new geometry in block generation + */ +public interface GeometryProducer { + /** + * @param blockId + * ID of block to generate geometry for + * @param geometryHandler + * geometry handler to use for setting geometry data + * @param blockProvider + * blockprovider can give access to neighbouring blocks etc + * @param blockUtil + * block util gives information about a block, tile texture coords etc + * @param x + * world space x position + * @param y + * world space y position + * @param z + * world space z position + */ + void generateBlock(final int blockId, final GeometryHandler geometryHandler, final BlockProvider blockProvider, + final BlockUtil blockUtil, final int x, final int y, final int z); +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/MeshProducer.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/MeshProducer.java new file mode 100644 index 0000000..252e532 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/geometryproducers/MeshProducer.java @@ -0,0 +1,130 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util.geometryproducers; + +import java.nio.FloatBuffer; +import java.util.EnumMap; +import java.util.Map; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Quaternion; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector2; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.scenegraph.IndexBufferData; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.MeshData; +import com.ardorcraft.util.BlockUtil; +import com.ardorcraft.world.BlockProvider; +import com.ardorcraft.world.BlockSide; +import com.ardorcraft.world.GeometryHandler; + +/** + * GeometryProducer that can take an Ardor3D Mesh as input. + */ +public final class MeshProducer implements GeometryProducer { + + private final Map<BlockSide, MeshData> meshDatas = new EnumMap<>(BlockSide.class); + private boolean transformCoords; + + public MeshProducer(final Mesh mesh) { + final MeshData data = mesh.getMeshData(); + for (final BlockSide side : BlockSide.values()) { + meshDatas.put(side, data); + } + + if (data.getIndexMode(0) != IndexMode.Triangles) { + throw new IllegalArgumentException("Only meshes with IndexMode.Triangles allowed"); + } + } + + public void createOrientations() { + MeshData copy = meshDatas.get(BlockSide.Bottom).makeCopy(); + + copy.rotatePoints(new Quaternion().fromAngleAxis(MathUtils.HALF_PI, Vector3.UNIT_X)); + meshDatas.put(BlockSide.Front, copy); + + final Quaternion rotation = new Quaternion().fromAngleAxis(-MathUtils.HALF_PI, Vector3.UNIT_Y); + + copy = copy.makeCopy(); + copy.rotatePoints(rotation); + meshDatas.put(BlockSide.Left, copy); + + copy = copy.makeCopy(); + copy.rotatePoints(rotation); + meshDatas.put(BlockSide.Back, copy); + + copy = copy.makeCopy(); + copy.rotatePoints(rotation); + meshDatas.put(BlockSide.Right, copy); + + copy = copy.makeCopy(); + copy.rotatePoints(new Quaternion().fromAngleAxis(-MathUtils.HALF_PI, Vector3.UNIT_Z)); + meshDatas.put(BlockSide.Top, copy); + } + + public void setTransformTextureCoords(final boolean value) { + transformCoords = value; + } + + @Override + public void generateBlock(final int blockId, final GeometryHandler geometryHandler, + final BlockProvider blockProvider, final BlockUtil blockUtil, final int x, final int y, final int z) { + + final BlockSide orientation = BlockSide.values()[blockProvider.getBlockExtra(x, y, z)]; + final MeshData data = meshDatas.get(orientation); + + if (geometryHandler.hasVertices()) { + final FloatBuffer vertices = data.getVertexBuffer(); + for (int i = 0; i < data.getVertexCount(); i++) { + geometryHandler.setVertex(i, vertices.get(i * 3 + 0) + x + 0.5f, vertices.get(i * 3 + 1) + y + 0.5f, + vertices.get(i * 3 + 2) + z + 0.5f); + } + } + + if (geometryHandler.hasTextureCoords()) { + final FloatBuffer texcoords = data.getTextureBuffer(0); + if (!transformCoords) { + for (int i = 0; i < data.getVertexCount(); i++) { + geometryHandler.setTextureCoord(i, texcoords.get(i * 2 + 0), texcoords.get(i * 2 + 1)); + } + } else { + final ReadOnlyVector2 coord = blockUtil.getBlockTextureCoord(blockId, BlockSide.Front); + + for (int i = 0; i < data.getVertexCount(); i++) { + geometryHandler.setTextureCoord( + i, + coord.getXf() + blockUtil.getOffsetWidth() + texcoords.get(i * 2 + 0) + * (blockUtil.getTileWidth() - blockUtil.getOffsetWidth() * 2), + coord.getYf() + blockUtil.getOffsetHeight() + texcoords.get(i * 2 + 1) + * (blockUtil.getTileHeight() - blockUtil.getOffsetHeight() * 2)); + } + } + } + + if (geometryHandler.hasColors()) { + final float globalLighting = geometryHandler.getGlobalLighting(x, y + 1, z); + final float localLighting = geometryHandler.getLocalLighting(x, y + 1, z); + + for (int i = 0; i < data.getVertexCount(); i++) { + geometryHandler.setColor(i, globalLighting, localLighting); + } + } + + int indexCount = 0; + if (geometryHandler.hasIndices()) { + final IndexBufferData<?> indices = data.getIndices(); + indexCount = indices.limit(); + for (int i = 0; i < indexCount; i++) { + geometryHandler.setIndex(i, indices.get(i)); + } + } + + geometryHandler.setVertexCount(data.getVertexCount()); + geometryHandler.setIndexCount(indexCount); + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Coords.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Coords.java new file mode 100644 index 0000000..83b9dc8 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Coords.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util.queue; + +/** + * 2D coordinates + */ +public class Coords { + private int x, z; + + public int getX() { + return x; + } + + public void setX(final int x) { + this.x = x; + } + + public int getZ() { + return z; + } + + public void setZ(final int z) { + this.z = z; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + x; + result = prime * result + z; + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof Coords)) { + return false; + } + final Coords other = (Coords) obj; + if (x != other.x) { + return false; + } + if (z != other.z) { + return false; + } + return true; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Task.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Task.java new file mode 100644 index 0000000..8b60558 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Task.java @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util.queue; + +import java.util.concurrent.atomic.AtomicLong; + +import com.ardor3d.renderer.Renderer; + +/** + * A prioritized Task for the Worker. Sorted based on bucket, distance and time. + */ +public abstract class Task implements Comparable<Task> { + public abstract void execute(Renderer renderer); + + private static AtomicLong atomicTime = new AtomicLong(); + private Coords playerCoords; + + private final int bucket; + private final int x; + private final int z; + private final long time; + + public Task(final int bucket, final int x, final int z) { + this.bucket = bucket; + this.x = x; + this.z = z; + time = atomicTime.incrementAndGet(); + } + + public int compareToTest(final Task o2) { + if (bucket < 2 && o2.bucket >= 2 || bucket >= 2 && o2.bucket < 2) { + final int bucketDiff = bucket - o2.bucket; + if (bucketDiff != 0) { + return bucketDiff; + } + } + + final int diffX1 = x - playerCoords.getX(); + final int diffZ1 = z - playerCoords.getZ(); + final int manhattanDistance1 = Math.abs(diffX1) + Math.abs(diffZ1); + final int diffX2 = o2.x - playerCoords.getX(); + final int diffZ2 = o2.z - playerCoords.getZ(); + final int manhattanDistance2 = Math.abs(diffX2) + Math.abs(diffZ2); + if (manhattanDistance1 != manhattanDistance2) { + return manhattanDistance1 - manhattanDistance2; + } + + final int bucketDiff = bucket - o2.bucket; + if (bucketDiff != 0) { + return bucketDiff; + } + + return (int) (time - o2.time); + } + + @Override + public int compareTo(final Task o2) { + final int bucketDiff = bucket - o2.bucket; + if (bucketDiff != 0) { + return bucketDiff; + } + + final int diffX1 = x - playerCoords.getX(); + final int diffZ1 = z - playerCoords.getZ(); + final int manhattanDistance1 = Math.abs(diffX1) + Math.abs(diffZ1); + final int diffX2 = o2.x - playerCoords.getX(); + final int diffZ2 = o2.z - playerCoords.getZ(); + final int manhattanDistance2 = Math.abs(diffX2) + Math.abs(diffZ2); + if (manhattanDistance1 != manhattanDistance2) { + return manhattanDistance1 - manhattanDistance2; + } + + return (int) (time - o2.time); + } + + public void setPlayerCoords(final Coords playerCoords) { + this.playerCoords = playerCoords; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("Task [bucket="); + builder.append(bucket); + builder.append(", x="); + builder.append(x); + builder.append(", z="); + builder.append(z); + builder.append(", time="); + builder.append(time); + builder.append("]"); + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + x; + result = prime * result + z; + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final Task other = (Task) obj; + if (x != other.x) { + return false; + } + if (z != other.z) { + return false; + } + return true; + } + + public int getBucket() { + return bucket; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Worker.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Worker.java new file mode 100644 index 0000000..deab168 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/Worker.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util.queue; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.locks.ReentrantLock; + +import com.ardor3d.renderer.Renderer; + +/** + * The Worker handles the chunk update Tasks, making sure they are prioritized, and doesnt spend too much time per + * frame. + */ +public class Worker { + private final List<Task> taskList = new LinkedList<>(); + private final ReentrantLock lock = new ReentrantLock(); + private long executionTime = 10; + private boolean needsSorting = false; + private final Coords playerCoords = new Coords(); + + public void enqueue(final Task task) { + task.setPlayerCoords(playerCoords); + + lock.lock(); + try { + taskList.add(task); + needsSorting = true; + } finally { + lock.unlock(); + } + } + + private final Set<Task> taskCleaner = new HashSet<>(); + + public void execute(final Renderer renderer) { + final long beginTime = System.currentTimeMillis(); + long elapsedTime = 0; + + int size = 0; + + lock.lock(); + try { + size = taskList.size(); + if (size > 0 && needsSorting) { + Collections.sort(taskList); + needsSorting = false; + + taskCleaner.clear(); + final Iterator<Task> iterator = taskList.iterator(); + while (iterator.hasNext()) { + final Task task = iterator.next(); + final int bucket = task.getBucket(); + if ((bucket == 2 || bucket == 3) && taskCleaner.contains(task)) { + iterator.remove(); + } else if (bucket == 2 || bucket == 3) { + taskCleaner.add(task); + } + } + size = taskList.size(); + } + } finally { + lock.unlock(); + } + + if (size > 0) { + int tasksFinished = 0; + for (int i = 0; i < size && elapsedTime < executionTime; i++) { + taskList.get(i).execute(renderer); + // System.out.println(taskList.get(i)); + tasksFinished++; + elapsedTime = System.currentTimeMillis() - beginTime; + } + + lock.lock(); + try { + for (int i = 0; i < tasksFinished; i++) { + taskList.remove(0); + } + } finally { + lock.unlock(); + } + } + } + + public long getExecutionTime() { + return executionTime; + } + + public void setExecutionTime(final long executionTime) { + this.executionTime = executionTime; + } + + public void setPlayerCoords(final int playerX, final int playerZ) { + playerCoords.setX(playerX); + playerCoords.setZ(playerZ); + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/WorkerManager.java b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/WorkerManager.java new file mode 100644 index 0000000..25490b0 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/util/queue/WorkerManager.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.util.queue; + +public final class WorkerManager { + private static final Worker worker = new Worker(); + + private WorkerManager() { + + } + + public static Worker getWorker() { + return worker; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/MeshVoxelationContext.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/MeshVoxelationContext.java new file mode 100644 index 0000000..23e3483 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/MeshVoxelationContext.java @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.voxel; + +import java.util.Arrays; + +import com.ardor3d.math.Transform; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; + +public class MeshVoxelationContext extends VoxelationContext { + public final Mesh mesh = new Mesh(); + public final Triangle triangle = new Triangle(); + public final Transform transform = new Transform(); + + public int[] _sectionVoxelCount = new int[3]; + public int[] _sectionCount = new int[3]; + + public float EDGE_EPSILON = 0.01f; + + public Section[][][] sections; + + public MeshVoxelationContext(final ReadOnlyVector3 center, final ReadOnlyVector3 voxelSize, + final int[] sectionCount, final int[] sectionVoxelCount) { + super(center, voxelSize); + + System.arraycopy(sectionVoxelCount, 0, _sectionVoxelCount, 0, 3); + System.arraycopy(sectionCount, 0, _sectionCount, 0, 3); + + extent[0] = sectionCount[0] * sectionVoxelCount[0]; + extent[1] = sectionCount[1] * sectionVoxelCount[1]; + extent[2] = sectionCount[2] * sectionVoxelCount[2]; + + sections = new Section[sectionCount[0]][sectionCount[1]][sectionCount[2]]; + for (int i = 0; i < sectionCount[0]; i++) { + for (int j = 0; j < sectionCount[1]; j++) { + for (int k = 0; k < sectionCount[2]; k++) { + sections[i][j][k] = new Section(sectionVoxelCount); + } + } + } + } + + @Override + public void init() {} + + public static class Section { + public final byte[][][] voxel; + + public Section(final int[] sectionVoxelCount) { + voxel = new byte[sectionVoxelCount[0]][sectionVoxelCount[1]][sectionVoxelCount[2]]; + } + + public void addVoxel(final byte mask, final int i, final int j, final int k) { + voxel[i][j][k] |= mask; + } + + public void clear() { + for (final byte[][] b1 : voxel) { + for (final byte[] b2 : b1) { + Arrays.fill(b2, (byte) 0); + } + } + } + + } + + public class Mesh { + public final Vector3 extent = new Vector3(); + public final Vector3 center = new Vector3(); + } + + public class Triangle { + public final Vector3[] vertex = new Vector3[] { new Vector3(), new Vector3(), new Vector3() }; + public final Vector3 normal = new Vector3(); + } + + public void clearVoxelArea() { + for (final Section[][] s1 : sections) { + for (final Section[] s2 : s1) { + for (int i = 0; i < s2.length; i++) { + s2[i].clear(); + } + } + } + } + +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/TriangleAABBIntersection.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/TriangleAABBIntersection.java new file mode 100644 index 0000000..f32e45f --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/TriangleAABBIntersection.java @@ -0,0 +1,268 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.voxel; + +import static java.lang.Math.abs; +import static java.lang.Math.max; +import static java.lang.Math.min; + +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardorcraft.voxel.MeshVoxelationContext.Triangle; + +/** + * ported from: http://jgt.akpeters.com/papers/AkenineMoller01/tribox.html + */ +public class TriangleAABBIntersection { + public static final boolean intersect(final ReadOnlyVector3 center, final ReadOnlyVector3 extent, + final Triangle triangle) { + return intersect(center.getX(), center.getY(), center.getZ(), extent, triangle); + } + + /** + * Test if Triangle and AABB instersects Will also set triangle.normal + * + * @param cx + * - AABB center x value + * @param cy + * - AABB center x value + * @param cz + * - AABB center x value + * @param extent + * - AABB extent of sides + * @param triangle + * @return true if intersecting + */ + public static final boolean intersect(final double cx, final double cy, final double cz, + final ReadOnlyVector3 extent, final Triangle triangle) { + double v0x = triangle.vertex[0].getX(), v0y = triangle.vertex[0].getY(), v0z = triangle.vertex[0].getZ(), v1x = triangle.vertex[1] + .getX(), v1y = triangle.vertex[1].getY(), v1z = triangle.vertex[1].getZ(), v2x = triangle.vertex[2] + .getX(), v2y = triangle.vertex[2].getY(), v2z = triangle.vertex[2].getZ(); + final double xExt = extent.getX(), yExt = extent.getY(), zExt = extent.getZ(); + + // Move everything so that the AABB center is in (0,0,0) + v0x -= cx; + v0y -= cy; + v0z -= cz; + v1x -= cx; + v1y -= cy; + v1z -= cz; + v2x -= cx; + v2y -= cy; + v2z -= cz; + + // Triangle edges + final double e0x = v1x - v0x, e0y = v1y - v0y, e0z = v1z - v0z, e1x = v2x - v1x, e1y = v2y - v1y, e1z = v2z + - v1z, e2x = v0x - v2x, e2y = v0y - v2y, e2z = v0z - v2z; + + // Test A: + // Use separating plane(axis) theorem to test overlap between triangle and box + // need to test for overlap in these directions: + // Cross product(edge from tri, {x,y,z}-direction) + // this gives 3x3=9 more tests + // test the 9 tests first (this was faster) + double min, max; + double p0, p1, p2, rad; + double fex = abs(e0x), fey = abs(e0y), fez = abs(e0z); + + // AXISTEST_X01(e0[Z], e0[Y], fez, fey); + p0 = e0z * v0y - e0y * v0z; + p2 = e0z * v2y - e0y * v2z; + min = min(p0, p2); + max = max(p0, p2); + rad = fez * yExt + fey * zExt; + if (min > rad || max < -rad) { + return false; + } + + // AXISTEST_Y02(e0[Z], e0[X], fez, fex); + p0 = -e0z * v0x + e0x * v0z; + p2 = -e0z * v2x + e0x * v2z; + min = min(p0, p2); + max = max(p0, p2); + rad = fez * xExt + fex * zExt; + if (min > rad || max < -rad) { + return false; + } + + // AXISTEST_Z12(e0[Y], e0[X], fey, fex); + p1 = e0y * v1x - e0x * v1y; + p2 = e0y * v2x - e0x * v2y; + min = min(p1, p2); + max = max(p1, p2); + rad = fey * xExt + fex * yExt; + if (min > rad || max < -rad) { + return false; + } + + fex = abs(e1x); + fey = abs(e1y); + fez = abs(e1z); + + // AXISTEST_X01(e1[Z], e1[Y], fez, fey); + p0 = e1z * v0y - e1y * v0z; + p2 = e1z * v2y - e1y * v2z; + min = min(p0, p2); + max = max(p0, p2); + rad = fez * yExt + fey * zExt; + if (min > rad || max < -rad) { + return false; + } + + // AXISTEST_Y02(e1[Z], e1[X], fez, fex); + p0 = -e1z * v0x + e1x * v0z; + p2 = -e1z * v2x + e1x * v2z; + min = min(p0, p2); + max = max(p0, p2); + rad = fez * xExt + fex * zExt; + if (min > rad || max < -rad) { + return false; + } + + // AXISTEST_Z0(e1[Y], e1[X], fey, fex); + p0 = e1y * v0x - e1x * v0y; + p1 = e1y * v1x - e1x * v1y; + min = min(p0, p1); + max = max(p0, p1); + rad = fey * xExt + fex * yExt; + if (min > rad || max < -rad) { + return false; + } + + fex = abs(e2x); + fey = abs(e2y); + fez = abs(e2z); + + // AXISTEST_X2(e2[Z], e2[Y], fez, fey); + p0 = e2z * v0y - e2y * v0z; + p1 = e2z * v1y - e2y * v1z; + min = min(p0, p1); + max = max(p0, p1); + rad = fez * yExt + fey * zExt; + if (min > rad || max < -rad) { + return false; + } + + // AXISTEST_Y1(e2[Z], e2[X], fez, fex); + p0 = -e2z * v0x + e2x * v0z; + p1 = -e2z * v1x + e2x * v1z; + min = min(p0, p1); + max = max(p0, p1); + rad = fez * xExt + fex * yExt; + if (min > rad || max < -rad) { + return false; + } + + // AXISTEST_Z12(e2[Y], e2[X], fey, fex); + p1 = e2y * v1x - e2x * v1y; + p2 = e2y * v2x - e2x * v2y; + min = min(p1, p2); + max = max(p1, p2); + rad = fey * xExt + fex * yExt; + if (min > rad || max < -rad) { + return false; + } + + // === Test 2 === + // check if a minimal AABB around the triangle intersect the given AABB + + double minX = v0x, maxX = v0x, minY = v0y, maxY = v0y, minZ = v0z, maxZ = v0z; + + // test in X-direction + if (v1x < minX) { + minX = v1x; + } + if (v2x < minX) { + minX = v2x; + } + if (v1x > maxX) { + maxX = v1x; + } + if (v2x > maxX) { + maxX = v2x; + } + if (minX > xExt || maxX < -xExt) { + return false; + } + + // test in Y-direction + if (v1y < minY) { + minY = v1y; + } + if (v2y < minY) { + minY = v2y; + } + if (v1y > maxY) { + maxY = v1y; + } + if (v2y > maxY) { + maxY = v2y; + } + if (minY > yExt || maxY < -yExt) { + return false; + } + + // test in Z-direction + if (v1z < minZ) { + minZ = v1z; + } + if (v2z < minZ) { + minZ = v2z; + } + if (v1z > maxZ) { + maxZ = v1z; + } + if (v2z > maxZ) { + maxZ = v2z; + } + if (minZ > zExt || maxZ < -zExt) { + return false; + } + + // === Test 3 === + // Check if the AABB intersects the plane of the triangle + // This is done by checking if the farthest and closest vertex + // of the AABB is on the same side of the plane + // normal = e0 x e1 + final double nx = e1y * e0z - e1z * e0y, ny = e1z * e0x - e1x * e0z, nz = e1x * e0y - e1y * e0x, d = nx * v0x + + ny * v0y + nz * v0z; + + triangle.normal.set(nx, ny, nz).normalizeLocal(); + + if (nx > 0) { + minX = -xExt; + maxX = xExt; + } else { + minX = xExt; + maxX = -xExt; + } + + if (ny > 0) { + minY = -yExt; + maxY = yExt; + } else { + minY = yExt; + maxY = -yExt; + } + + if (nz > 0) { + minZ = -zExt; + maxZ = zExt; + } else { + minZ = zExt; + maxZ = -zExt; + } + + if (nx * minX + ny * minY + nz * minZ > d) { + return false; + } + if (nx * maxX + ny * maxY + nz * maxZ < d) { + return false; + } + + return true; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxel.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxel.java new file mode 100644 index 0000000..1bf3357 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxel.java @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.voxel; + +public class Voxel { + public int x, y, z; +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelateMesh.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelateMesh.java new file mode 100644 index 0000000..86d411b --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelateMesh.java @@ -0,0 +1,288 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.voxel; + +import java.nio.FloatBuffer; + +import com.ardor3d.math.Vector3; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.MeshData; +import com.ardorcraft.voxel.MeshVoxelationContext.Section; +import com.ardorcraft.voxel.MeshVoxelationContext.Triangle; + +public class VoxelateMesh { + public static final byte EDGE_DIRECTION_RIGHT = 0x01; + public static final byte EDGE_DIRECTION_LEFT = 0x02; + + private final static int X = 0; + private final static int Y = 1; + private final static int Z = 2; + + public static void surface(final Mesh mesh, final MeshVoxelationContext vcx) { + voxelate(mesh, vcx, false); + } + + public static void solid(final Mesh mesh, final MeshVoxelationContext vcx) { + voxelate(mesh, vcx, true); + } + + private static void voxelate(final Mesh mesh, final MeshVoxelationContext vcx, final boolean isSolid) { + if (mesh.getMeshData().getVertexBuffer() == null) { + return; + } + + vcx.clearVoxelArea(); + + calculateMeshTransform(mesh, vcx); + + final MeshData md = mesh.getMeshData(); + final int sectionCount = md.getSectionCount(); + int primitiveCount; + if (md.getIndexMode(0) == IndexMode.Triangles) { + final Vector3[] vertexes = vcx.triangle.vertex; + for (int j = 0; j < sectionCount; j++) { + primitiveCount = md.getPrimitiveCount(j); + for (int i = 0; i < primitiveCount; i++) { + md.getPrimitiveVertices(i, j, vertexes); + voxelateTriangle(vcx); + } + } + } + + if (isSolid) { + fill(vcx); + } else { + final Section section = vcx.sections[0][0][0]; + final int[] sectionIndex = new int[] { + 0, 0, 0 + }; + final int[] voxelIndex = new int[3]; + byte[][] slice; + byte[] line; + byte mask; + for (int i = 0; i < vcx._sectionVoxelCount[0]; i++) { + slice = section.voxel[i]; + for (int j = 0; j < vcx._sectionVoxelCount[1]; j++) { + line = slice[j]; + for (int k = 0; k < vcx._sectionVoxelCount[2]; k++) { + mask = line[k]; + if (mask == 0) { + voxelIndex[0] = i; + voxelIndex[1] = j; + voxelIndex[2] = k; + vcx.addSpace(sectionIndex, voxelIndex); + continue; + } + voxelIndex[0] = i; + voxelIndex[1] = j; + voxelIndex[2] = k; + vcx.addVoxel(sectionIndex, voxelIndex); + } + } + } + } + } + + private static void fill(final MeshVoxelationContext vcx) { + final Section section = vcx.sections[0][0][0]; + final int[] sectionIndex = new int[] { + 0, 0, 0 + }; + final int[] voxelIndex = new int[3]; + byte[][] slice; + byte[] line; + byte mask; + boolean fill = false; + for (int i = 0; i < vcx._sectionVoxelCount[0]; i++) { + slice = section.voxel[i]; + for (int j = 0; j < vcx._sectionVoxelCount[1]; j++) { + line = slice[j]; + for (int k = 0; k < vcx._sectionVoxelCount[2]; k++) { + mask = line[k]; + if (mask == 0) { + if (!fill) { + voxelIndex[0] = i; + voxelIndex[1] = j; + voxelIndex[2] = k; + vcx.addSpace(sectionIndex, voxelIndex); + continue; + } + } else if ((mask & EDGE_DIRECTION_LEFT) != 0) { + fill = true; + } else if ((mask & EDGE_DIRECTION_RIGHT) != 0) { + fill = false; + } + voxelIndex[0] = i; + voxelIndex[1] = j; + voxelIndex[2] = k; + vcx.addVoxel(sectionIndex, voxelIndex); + } + } + } + } + + private static void voxelateTriangle(final MeshVoxelationContext vcx) { + final Triangle triangle = vcx.triangle; + Section section; + boolean isIntersecting; + int minX, maxX, minY, maxY, minZ, maxZ; + final Vector3 center = new Vector3(); + final Voxel vox = new Voxel(); + byte mask; + + final Vector3 v1 = triangle.vertex[0]; + final Vector3 v2 = triangle.vertex[1]; + final Vector3 v3 = triangle.vertex[2]; + + // If allowed it would be smarter to do the conversion in the mesh + // vertexes instead of for every primitive + v1.subtractLocal(vcx.mesh.center); + v1.multiplyLocal(vcx.transform.getScale()); + v1.addLocal(vcx.transform.getTranslation()); + + v2.subtractLocal(vcx.mesh.center); + v2.multiplyLocal(vcx.transform.getScale()); + v2.addLocal(vcx.transform.getTranslation()); + + v3.subtractLocal(vcx.mesh.center); + v3.multiplyLocal(vcx.transform.getScale()); + v3.addLocal(vcx.transform.getTranslation()); + + // Get the bounding box for this triangle + vcx.positionToVoxel(v1, vox); + minX = maxX = vox.x; + minY = maxY = vox.y; + minZ = maxZ = vox.z; + + vcx.positionToVoxel(v2, vox); + if (vox.x < minX) { + minX = vox.x; + } else if (vox.x > maxX) { + maxX = vox.x; + } + if (vox.y < minY) { + minY = vox.y; + } else if (vox.y > maxY) { + maxY = vox.y; + } + if (vox.z < minZ) { + minZ = vox.z; + } else if (vox.z > maxZ) { + maxZ = vox.z; + } + + vcx.positionToVoxel(v3, vox); + if (vox.x < minX) { + minX = vox.x; + } else if (vox.x > maxX) { + maxX = vox.x; + } + if (vox.y < minY) { + minY = vox.y; + } else if (vox.y > maxY) { + maxY = vox.y; + } + if (vox.z < minZ) { + minZ = vox.z; + } else if (vox.z > maxZ) { + maxZ = vox.z; + } + + // XXX: we should be able to skip checking two voxels but probably minor + // performance increase, the voxels of the triangle points + for (int i = minX; i <= maxX; i++) { + for (int j = minY; j <= maxY; j++) { + for (int k = minZ; k <= maxZ; k++) { + vcx.voxelToPosition(i, j, k, center); + isIntersecting = TriangleAABBIntersection.intersect(center, vcx.voxelSize, triangle); + if (isIntersecting) { + // Check if the projection of the triangle normal is + // positive or negative with regards to the slice axis(Z by default) + // TODO: Think i need to improve this fill algorithm it isn't 100% robust + final double d = triangle.normal.dot(Vector3.UNIT_Z); + // XXX: don't know the best limit of this EPSILON. to low and we miss fill stops + if (d > vcx.EDGE_EPSILON) { + mask = EDGE_DIRECTION_LEFT; + } else { + mask = EDGE_DIRECTION_RIGHT; + } + + // TODO: Decide which section this voxel belongs to + section = vcx.sections[0][0][0]; + section.addVoxel(mask, i, j, k); + } + } + } + } + } + + private static void calculateMeshTransform(final Mesh mesh, final MeshVoxelationContext vcx) { + float x, y, z, minX, maxX, minY, maxY, minZ, maxZ; + final FloatBuffer vBuf = mesh.getMeshData().getVertexBuffer(); + + minX = maxX = vBuf.get(X); + minY = maxY = vBuf.get(Y); + minZ = maxZ = vBuf.get(Z); + + final int vCount = 3 * mesh.getMeshData().getVertexCount(); + for (int i = 0; i < vCount; i += 3) { + x = vBuf.get(i); + y = vBuf.get(i + 1); + z = vBuf.get(i + 2); + + if (x < minX) { + minX = x; + } else if (x > maxX) { + maxX = x; + } + + if (y < minY) { + minY = y; + } else if (y > maxY) { + maxY = y; + } + + if (z < minZ) { + minZ = z; + } else if (z > maxZ) { + maxZ = z; + } + } + + vcx.mesh.extent.set(maxX - minX, maxY - minY, maxZ - minZ); + vcx.mesh.center.set((minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2); + + // -1 because we want the mesh to be scaled a tiny bit smaller than the entire VoxelationArea + final double xScale = vcx.voxelSize.getX() * (vcx._sectionCount[X] * vcx._sectionVoxelCount[X] - 1) + / vcx.mesh.extent.getX(); + final double yScale = vcx.voxelSize.getY() * (vcx._sectionCount[Y] * vcx._sectionVoxelCount[Y] - 1) + / vcx.mesh.extent.getY(); + final double zScale = vcx.voxelSize.getZ() * (vcx._sectionCount[Z] * vcx._sectionVoxelCount[Z] - 1) + / vcx.mesh.extent.getZ(); + + // Setup transform so that Mesh is strictly in +X,+Y,+Z quadrant and scaled so that it + // fits inside the VoxelationArea + double scale; + if (xScale < yScale) { + if (xScale < zScale) { + scale = xScale; + } else { + scale = zScale; + } + } else { + if (yScale < zScale) { + scale = yScale; + } else { + scale = zScale; + } + } + vcx.transform.setScale(scale); + vcx.transform.setTranslation(0.5 * vcx.mesh.extent.getX() * scale, 0.5 * vcx.mesh.extent.getY() * scale, 0.5 + * vcx.mesh.extent.getZ() * scale); + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationContext.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationContext.java new file mode 100644 index 0000000..e8eb08f --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationContext.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.voxel; + +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyVector3; + +public abstract class VoxelationContext { + // The center position of voxel 0,0,0 + public final Vector3 center = new Vector3(); + // The size of one voxel + public final Vector3 voxelSize = new Vector3(); + + public int[] extent = new int[] { + 32, 32, 32 + }; + + public VoxelationListener listener; + + public VoxelationContext(final ReadOnlyVector3 center, final ReadOnlyVector3 voxelSize) { + this.center.set(center); + this.voxelSize.set(voxelSize); + } + + public abstract void init(); + + public void positionToVoxel(final ReadOnlyVector3 pos, final Voxel store) { + store.x = (int) Math.round((pos.getX() - center.getX()) / voxelSize.getX()); + store.y = (int) Math.round((pos.getY() - center.getY()) / voxelSize.getY()); + store.z = (int) Math.round((pos.getZ() - center.getZ()) / voxelSize.getZ()); + } + + public void voxelToPosition(final Voxel voxel, final Vector3 store) { + store.set(voxel.x * voxelSize.getX() + center.getX(), voxel.y * voxelSize.getY() + center.getY(), voxel.z + * voxelSize.getZ() + center.getZ()); + } + + public void voxelToPosition(final int[] voxel, final Vector3 store) { + store.set(voxel[0] * voxelSize.getX() + center.getX(), voxel[1] * voxelSize.getY() + center.getY(), voxel[2] + * voxelSize.getZ() + center.getZ()); + } + + public void voxelToPosition(final int x, final int y, final int z, final Vector3 store) { + store.set(x * voxelSize.getX() + center.getX(), y * voxelSize.getY() + center.getY(), z * voxelSize.getZ() + + center.getZ()); + } + + public void addVoxel(final int[] section, final int[] voxel) { + if (listener != null) { + listener.addVoxel(this, section, voxel); + } + } + + public void addSpace(final int[] section, final int[] voxel) { + if (listener != null) { + listener.addSpace(this, section, voxel); + } + } + +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationListener.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationListener.java new file mode 100644 index 0000000..593a460 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/VoxelationListener.java @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.voxel; + +public interface VoxelationListener { + public void addVoxel(VoxelationContext vcx, int[] section, int[] voxel); + + public void addSpace(VoxelationContext vcx, int[] section, int[] voxel); +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxelator.java b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxelator.java new file mode 100644 index 0000000..aee5f37 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/voxel/Voxelator.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.voxel; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.scenegraph.Mesh; +import com.ardorcraft.data.Pos; +import com.ardorcraft.world.ChunkModifier; + +public class Voxelator { + private MeshVoxelationContext vcx; + private final ChunkModifier blockScene; + private final Pos position = new Pos(); + private int blockType; + + private final int xSize; + private final int ySize; + private final int zSize; + + public Voxelator(final ChunkModifier blockScene, final int xSize, final int ySize, final int zSize) { + this.blockScene = blockScene; + this.xSize = xSize; + this.ySize = ySize; + this.zSize = zSize; + } + + public void voxelate(final Vector3 position, final Mesh mesh, final float edgeEpsilon, final int blockType) { + final int X = MathUtils.floor(position.getXf()); + final int Y = MathUtils.floor(position.getYf()); + final int Z = MathUtils.floor(position.getZf()); + + voxelate(new Pos(X, Y, Z), mesh, edgeEpsilon, blockType); + } + + public void voxelate(final Pos pos, final Mesh mesh, final float edgeEpsilon, final int blockType) { + if (vcx == null) { + init(); + } + + this.blockType = blockType; + position.set(pos.x - xSize / 2, pos.y, pos.z - zSize / 2); + + vcx.EDGE_EPSILON = edgeEpsilon; + + VoxelateMesh.solid(mesh, vcx); + } + + protected void init() { + vcx = new MeshVoxelationContext(Vector3.ZERO, new Vector3(1, 1, 1), new int[] { + 1, 1, 1 + }, new int[] { + xSize, ySize, zSize + }); + + vcx.listener = new VoxelationListener() { + Vector3 pos = new Vector3(); + + @Override + public void addVoxel(final VoxelationContext context, final int[] section, final int[] voxel) { + vcx.voxelToPosition(voxel, pos); + + final int X = MathUtils.floor(pos.getXf()); + final int Y = MathUtils.floor(pos.getYf()); + final int Z = MathUtils.floor(pos.getZf()); + + blockScene.setBlock(X + position.x, Y + position.y, Z + position.z, blockType); + } + + @Override + public void addSpace(final VoxelationContext vcx, final int[] section, final int[] voxel) { + // TODO Auto-generated method stub + + } + }; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockEditData.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockEditData.java new file mode 100644 index 0000000..b37f544 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockEditData.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +import com.ardorcraft.data.Pos; + +public class BlockEditData { + public Pos pos; + public int type; + public BlockSide orientation; + + public BlockEditData(final Pos pos, final int type, final BlockSide orientation) { + this.pos = pos; + this.type = type; + this.orientation = orientation; + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("BlockEditData [pos="); + builder.append(pos); + builder.append(", type="); + builder.append(type); + builder.append(", orientation="); + builder.append(orientation); + builder.append("]"); + return builder.toString(); + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockProvider.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockProvider.java new file mode 100644 index 0000000..075cc61 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockProvider.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +/** + * Proxy for serving up block data. + */ +public interface BlockProvider { + + /** + * Get block id for world coordinate x, y, z + * + * @param x + * X coordinate + * @param y + * Y coordinate + * @param z + * Z coordinate + * @return Block id at position + */ + int getBlock(int x, int y, int z); + + /** + * Get block extra data for world coordinate x, y, z.<br> + * bits 0,1,2 = orientation<br> + * bits 3,4,5,6 = age (or something else)<br> + * bit 7 = whatever + * <p> + * + * @param x + * X coordinate + * @param y + * Y coordinate + * @param z + * Z coordinate + * @return Block id at position + */ + int getBlockExtra(int x, final int y, int z); +}
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockSide.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockSide.java new file mode 100644 index 0000000..ac51f43 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockSide.java @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +/** + * Used to identify the various sides of a block. + */ +public enum BlockSide { + Front, + Back, + Left, + Right, + Top, + Bottom +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockType.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockType.java new file mode 100644 index 0000000..a6de24d --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockType.java @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +/** + * Identifies if blocks are see through or covers the entire 1x1x1 box (which then allows for hidden surface removal). + */ +public enum BlockType { + Solid, + Transparent +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockWorld.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockWorld.java new file mode 100644 index 0000000..f05e1df --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/BlockWorld.java @@ -0,0 +1,1979 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.imageio.ImageIO; + +import com.ardor3d.bounding.BoundingBox; +import com.ardor3d.image.Image; +import com.ardor3d.image.Texture; +import com.ardor3d.image.Texture.ApplyMode; +import com.ardor3d.image.Texture.MagnificationFilter; +import com.ardor3d.image.Texture.WrapMode; +import com.ardor3d.image.Texture2D; +import com.ardor3d.image.TextureStoreFormat; +import com.ardor3d.math.ColorRGBA; +import com.ardor3d.math.MathUtils; +import com.ardor3d.math.Vector3; +import com.ardor3d.math.type.ReadOnlyColorRGBA; +import com.ardor3d.math.type.ReadOnlyVector3; +import com.ardor3d.renderer.ContextManager; +import com.ardor3d.renderer.IndexMode; +import com.ardor3d.renderer.Renderer; +import com.ardor3d.renderer.state.BlendState; +import com.ardor3d.renderer.state.CullState; +import com.ardor3d.renderer.state.TextureState; +import com.ardor3d.scenegraph.AbstractBufferData.VBOAccessMode; +import com.ardor3d.scenegraph.Mesh; +import com.ardor3d.scenegraph.MeshData; +import com.ardor3d.scenegraph.Node; +import com.ardor3d.scenegraph.hint.CullHint; +import com.ardor3d.scenegraph.hint.DataMode; +import com.ardor3d.scenegraph.hint.LightCombineMode; +import com.ardor3d.util.ReadOnlyTimer; +import com.ardor3d.util.TextureManager; +import com.ardor3d.util.geom.BufferUtils; + +import com.ardor3d.image.util.awt.AWTImageLoader; + +import com.ardorcraft.collision.HitTester; +import com.ardorcraft.collision.IntersectionResult; +import com.ardorcraft.collision.Tracer; +import com.ardorcraft.data.Pos; +import com.ardorcraft.util.BlockUtil; +import com.ardorcraft.util.ColorUtil; +import com.ardorcraft.util.DoubleBufferedList; +import com.ardorcraft.util.queue.Coords; +import com.ardorcraft.util.queue.Task; +import com.ardorcraft.util.queue.WorkerManager; +import com.ardorcraft.world.utils.ChunkDistanceComparator; + +/** + * BlockWorld is the main class when it comes to building and rendering the block world. It handles asynchronous + * updating and rebuilding of the chunks after receiving chunk or block updates. It also handles light calculations. + * <p> + * In the simplest setup, the blockworld is just initialized with a {@link WorldSettings} object. Then the main node for + * rendering the world is extracted and added to your scenegraph. Finally when you have everything else setup, all the + * threads are kicked off to start processing. + * <p> + * <code> + * blockWorld = new BlockWorld(settings);<br> + * root.attachChild(blockWorld.getWorldNode());<br> + * ...<br> + * blockWorld.startThreads(); + * </code> + */ +public final class BlockWorld implements LightProvider, ChunkModifier { + private static final Logger logger = Logger.getLogger(BlockWorld.class.getName()); + + public static int WATER = 223; + + private static final int CHUNK = 0; + private static final int BLOCKS = 1; + private static final int REBUILD = 2; + private static final int LIGHT = 3; + + private final int width; + private final int height; + private final int subMeshSize; + + private static final byte MAX_LIGHT = 15; + private static final byte LIGHT_STEP = 2; + private float globalLight = 1f; + private boolean lightingChanged = false; + private boolean updateLighting = true; + private boolean firstRender = true; + + private final ColorRGBA caveLight = new ColorRGBA(0.08f, 0.07f, 0.07f, 1.0f); + private final ColorRGBA nightLight = new ColorRGBA(0.1f, 0.16f, 0.2f, 1.0f); + private final ColorRGBA dayLight = new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); + private final ColorRGBA torchLight = new ColorRGBA(1.0f, 0.8f, 0.5f, 1.0f); + + private ByteBuffer lightingBuffer; + + private final byte blocks[]; + // Extra data + // 0,1,2 = orientation + // 3,4,5,6 = age (or something else) + // 7 = whatever + private final byte blockExtra[]; + + private BlockUtil blockUtil; + + private final short lightHeightmap[]; + private final byte lightingWrite[]; + private final byte localLightDataWrite[]; + + private final BitSet isLightSolid; + + private final Set<Pos> currentPos = new LinkedHashSet<>(); + private int oldX = Integer.MAX_VALUE; + private int oldZ = Integer.MAX_VALUE; + private int playerPositionX = Integer.MIN_VALUE; + private int playerPositionZ = Integer.MIN_VALUE; + + private Texture lightTexture; + private TextureState terrainTextureState; + private BlendState terrainBlendState; + // private TextureState waterTextureState; + // private Texture waterTexture; + + private BlendState transparentState; + private CullState cullState; + private CullState windowCullState; + + private Node worldNode; + private Node solidNode; + private Node transparentNode; + + private final IServerConnection serverConnection; + + private final Map<Pos, Mesh> meshCache = new HashMap<>(); + private final Map<Pos, Mesh> meshCacheTransparent = new HashMap<>(); + private final Set<Pos> isLoaded = Collections.synchronizedSet(new HashSet<Pos>()); + + private final DoubleBufferedList<Pos> lightUpdateBox = new DoubleBufferedList<>(); + private final DoubleBufferedList<UpdateMessage> chunkMailBox = new DoubleBufferedList<>(); + + private final int gridSize; + private int currentTileX; + private int currentTileZ; + private final int gridUnitSize; + + private final CountDownLatch exitLatch = new CountDownLatch(2); + private boolean threadsStarted = false; + private boolean exit = false; + + private final WorldSettings settings; + private boolean hasVBOSupport = false; + private boolean doDefaultTint = true; + + private final GeometryHandler geometryHandler; + private final float[] tmpVertices; + private final float[] tmpTexcoords; + private final float[] tmpColors; + private final int[] tmpIndices; + + private final GeometryHandler geometryHandlerTransparent; + private final float[] tmpVerticesTransparent; + private final float[] tmpTexcoordsTransparent; + private final float[] tmpColorsTransparent; + private final int[] tmpIndicesTransparent; + + /** + * Create a new BlockWorld instance based on the provided settings. + * + * @param settings + */ + public BlockWorld(final WorldSettings settings) { + Objects.requireNonNull(settings); + Objects.requireNonNull(settings.getServerConnection()); + + this.settings = new WorldSettings(settings); + width = settings.getGridSize() * settings.getTileSize(); + height = settings.getTileHeight(); + subMeshSize = settings.getTileSize(); + + doDefaultTint = settings.isDoDefaultTint(); + + hasVBOSupport = settings.isUseVBO() && ContextManager.getCurrentContext().getCapabilities().isVBOSupported(); + if (hasVBOSupport) { + logger.info("Using VBO"); + } + + WorkerManager.getWorker().setExecutionTime(8); + + gridUnitSize = settings.getGridSize(); + gridSize = width / 2; + + geometryHandler = new GeometryHandler(BlockWorld.this); + tmpVertices = new float[subMeshSize * height * subMeshSize * 24 * 3 / 2]; + tmpTexcoords = new float[subMeshSize * height * subMeshSize * 24 * 2 / 2]; + tmpColors = new float[subMeshSize * height * subMeshSize * 24 * 2 / 2]; + tmpIndices = new int[subMeshSize * height * subMeshSize * 36 * 1 / 2]; + + geometryHandlerTransparent = new GeometryHandler(BlockWorld.this); + tmpVerticesTransparent = new float[subMeshSize * height * subMeshSize * 24 * 3 / 2]; + tmpTexcoordsTransparent = new float[subMeshSize * height * subMeshSize * 24 * 2 / 2]; + tmpColorsTransparent = new float[subMeshSize * height * subMeshSize * 24 * 2 / 2]; + tmpIndicesTransparent = new int[subMeshSize * height * subMeshSize * 36 * 1 / 2]; + + blocks = new byte[width * height * width]; + blockExtra = new byte[width * height * width]; + + lightingWrite = new byte[width * height * width]; + lightHeightmap = new short[width * width]; + localLightDataWrite = new byte[width * height * width]; + + Arrays.fill(lightingWrite, (byte) 255); + + isLightSolid = new BitSet(); + + serverConnection = settings.getServerConnection(); + serverConnection.getModifier(this); + + createWorld(settings); + } + + private void createWorld(final WorldSettings settings) { + terrainTextureState = new TextureState(); + + final int blockSize = settings.getTerrainTextureTileSize(); + try { + BufferedImage img = ImageIO.read(settings.getTerrainTexture().openStream()); + + if (doDefaultTint) { + final List<Rectangle> areas = new ArrayList<>(); + areas.add(new Rectangle(0, 0, blockSize, blockSize)); + areas.add(new Rectangle(4 * blockSize, 3 * blockSize, blockSize, blockSize)); + try { + img = ColorUtil.tintAreas(img, areas, blockSize * 4 - 1, 0); + } catch (final Exception e) { + logger.log(Level.WARNING, "Tinting failed!", e); + } + } + final Image image = AWTImageLoader.makeArdor3dImage(img, true); + + final Texture texture1 = TextureManager.loadFromImage(image, + Texture.MinificationFilter.NearestNeighborLinearMipMap, TextureStoreFormat.GuessNoCompressedFormat); + texture1.setMagnificationFilter(MagnificationFilter.NearestNeighbor); + terrainTextureState.setTexture(texture1, 0); + + final int tw = texture1.getImage().getWidth(); + final int th = texture1.getImage().getHeight(); + logger.info("Texture size: " + tw + "x" + th); + + final int numLevels = (int) Math.floor(Math.log(blockSize) / Math.log(2)); + texture1.setTextureMaxLevel(numLevels); + + blockUtil = new BlockUtil(tw, th, blockSize); + } catch (final Exception e) { + throw new IllegalArgumentException(e); + } + + final BufferedImage image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + final ColorRGBA outLight = ColorRGBA.lerp(nightLight, dayLight, globalLight, new ColorRGBA()); + final ColorRGBA tmp1 = new ColorRGBA(); + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + final float global = x / 15f; + final float local = y / 15f; + + ColorRGBA.lerp(caveLight, outLight, global, tmp1); + tmp1.lerpLocal(torchLight, local); + + image.setRGB(x, y, tmp1.asIntARGB()); + } + } + final Image lightImage = AWTImageLoader.makeArdor3dImage(image, false); + lightTexture = TextureManager.loadFromImage(lightImage, Texture.MinificationFilter.BilinearNoMipMaps); + lightTexture.setApply(ApplyMode.Modulate); + lightTexture.setWrap(WrapMode.EdgeClamp); + terrainTextureState.setTexture(lightTexture, 1); + lightingBuffer = BufferUtils.createByteBuffer(16 * 16 * 4); + + // waterTextureState = new TextureState(); + // waterTexture = TextureManager.load(settings.getWaterTexture(), + // Texture.MinificationFilter.NearestNeighborLinearMipMap, TextureStoreFormat.GuessNoCompressedFormat, + // true); + // waterTexture.setMagnificationFilter(MagnificationFilter.NearestNeighbor); + // waterTextureState.setTexture(waterTexture, 0); + + terrainBlendState = new BlendState(); + terrainBlendState.setTestEnabled(true); + terrainBlendState.setBlendEnabled(false); + terrainBlendState.setReference(0.5f); + + transparentState = new BlendState(); + transparentState.setBlendEnabled(true); + + cullState = new CullState(); + cullState.setCullFace(CullState.Face.Back); + cullState.setEnabled(true); + + windowCullState = new CullState(); + windowCullState.setCullFace(CullState.Face.None); + windowCullState.setEnabled(true); + + worldNode = new WorldNode("WorldModifier"); + worldNode.getSceneHints().setCullHint(CullHint.Never); + + solidNode = new Node("Solid"); + solidNode.getSceneHints().setCullHint(CullHint.Never); + + transparentNode = new Node("Water"); + transparentNode.getSceneHints().setCullHint(CullHint.Never); + + worldNode.attachChild(solidNode); + worldNode.attachChild(transparentNode); + } + + private class WorldNode extends Node { + public WorldNode(final String name) { + super(name); + } + + @Override + public void draw(final Renderer renderer) { + if (!firstRender && updateLighting) { + updateLightingTexture(renderer); + updateLighting = false; + } + if (firstRender) { + firstRender = false; + } + + solidNode.draw(renderer); + transparentNode.draw(renderer); + } + + private final ColorRGBA resultColorFirst = new ColorRGBA(); + private final ColorRGBA resultColorFinal = new ColorRGBA(); + + private void updateLightingTexture(final Renderer renderer) { + ColorRGBA.lerp(nightLight, dayLight, globalLight, resultColorFirst); + + for (int x = 0; x < 16; x++) { + for (int y = 0; y < 16; y++) { + final float global = y / 15f; + final float local = x / 15f; + + ColorRGBA.lerp(caveLight, resultColorFirst, global, resultColorFinal); + resultColorFinal.lerpLocal(torchLight, local); + + lightingBuffer.put((byte) (resultColorFinal.getRed() * 255)); + lightingBuffer.put((byte) (resultColorFinal.getGreen() * 255)); + lightingBuffer.put((byte) (resultColorFinal.getBlue() * 255)); + lightingBuffer.put((byte) 255); + } + } + + lightingBuffer.flip(); + renderer.updateTexture2DSubImage((Texture2D) lightTexture, 0, 0, 16, 16, lightingBuffer, 0, 0, 16); + } + + @Override + public void onDraw(final Renderer renderer) { + draw(renderer); + } + } + + /** + * Start all processing threads. Currently a chunk updater and a light updater. + */ + public void startThreads() { + if (!threadsStarted) { + exit = false; + + final Thread chunkUpdater = new Thread(new ChunkUpdater(), "ChunkUpdater"); + chunkUpdater.setDaemon(true); + chunkUpdater.start(); + + final Thread lightUpdater = new Thread(new LightUpdater(), "LightUpdater"); + lightUpdater.setDaemon(true); + lightUpdater.start(); + + threadsStarted = true; + } + } + + /** + * Get the Node used for rendering the block world. + * + * @return Node with block world + */ + public Node getWorldNode() { + return worldNode; + } + + // private final Matrix4 textureMatrix = new Matrix4(); + + /** + * Issue world updates based on currently set player position/direction. + * + * @param timer + */ + public void update(final ReadOnlyTimer timer) { + // textureMatrix.setValue(3, 0, timer.getTimeInSeconds() * 0.2); + // waterTexture.setTextureMatrix(textureMatrix); + + if (playerPositionX == oldX && playerPositionZ == oldZ) { + if (lightingChanged) { + for (final Pos pos : currentPos) { + chunkMailBox.add(new LightMessage(pos.x, pos.z)); + } + lightingChanged = false; + } + return; + } + // final int diffX = playerPositionX - oldX; + // final int diffZ = playerPositionZ - oldZ; + oldX = playerPositionX; + oldZ = playerPositionZ; + + // TODO + serverConnection.update(playerPositionX, playerPositionZ); + + final Set<Pos> newPos = new LinkedHashSet<>(); + for (int x = 0; x < gridUnitSize; x++) { + for (int z = 0; z < gridUnitSize; z++) { + final int xx = playerPositionX + x - gridUnitSize / 2; + final int zz = playerPositionZ + z - gridUnitSize / 2; + newPos.add(new Pos(xx, 0, zz)); + } + } + + final Iterator<Pos> tileIterator = currentPos.iterator(); + while (tileIterator.hasNext()) { + final Pos pos = tileIterator.next(); + + if (!newPos.contains(pos)) { + isLoaded.remove(pos); + tileIterator.remove(); + } else { + newPos.remove(pos); + } + } + + if (!newPos.isEmpty()) { + final List<Pos> sortedPos = new ArrayList<>(); + sortedPos.addAll(newPos); + Collections.sort(sortedPos, new ChunkDistanceComparator(playerPositionX, playerPositionZ)); + + // TODO + for (final Pos coll : sortedPos) { + serverConnection.requestChunk(coll.x, coll.z); + } + } + + if (lightingChanged) { + for (final Pos pos : currentPos) { + chunkMailBox.add(new LightMessage(pos.x, pos.z)); + } + lightingChanged = false; + } + + currentPos.addAll(newPos); + } + + @Override + public void postChunk(final int x, final int z, final Chunk chunk) { + chunkMailBox.add(new ChunkMessage(x, z, chunk.getBlocks(), chunk.getExtra())); + } + + /** + * Let the block world know the current player position and direction. + * + * @param location + * @param direction + */ + public void updatePlayer(final ReadOnlyVector3 location, final ReadOnlyVector3 direction) { + serverConnection.updatePlayerPosition(location, direction); + + playerPositionX = MathUtils.floor(location.getXf() / subMeshSize); + playerPositionZ = MathUtils.floor(location.getZf() / subMeshSize); + + WorkerManager.getWorker().setPlayerCoords(playerPositionX, playerPositionZ); + + currentTileX = playerPositionX * subMeshSize; + currentTileZ = playerPositionZ * subMeshSize; + } + + /** + * Reload all chunks. + */ + public void reloadAll() { + currentPos.clear(); + oldX = Integer.MAX_VALUE; + oldZ = Integer.MAX_VALUE; + } + + private final class LightUpdater implements Runnable { + private final Set<Pos> updates = new LinkedHashSet<>(); + + @Override + public void run() { + while (!exit) { + final List<Pos> lightList = lightUpdateBox.switchAndGet(); + + if (!lightList.isEmpty()) { + updates.clear(); + + for (final Pos pos : lightList) { + if (pos == null) { + continue; + } + + final Pos gridpos = new Pos(MathUtils.floor((float) pos.x / subMeshSize), 0, + MathUtils.floor((float) pos.z / subMeshSize)); + + if (isChunkValid(gridpos.x, gridpos.z)) { + updates.add(gridpos); + } + + final int xUpdate = (float) MathUtils.moduloPositive(pos.x, subMeshSize) / (float) subMeshSize > 0.5f ? gridpos.x + 1 + : gridpos.x - 1; + final int zUpdate = (float) MathUtils.moduloPositive(pos.z, subMeshSize) / (float) subMeshSize > 0.5f ? gridpos.z + 1 + : gridpos.z - 1; + if (isChunkValid(xUpdate, gridpos.z)) { + updates.add(new Pos(xUpdate, 0, gridpos.z)); + } + if (isChunkValid(gridpos.x, zUpdate)) { + updates.add(new Pos(gridpos.x, 0, zUpdate)); + } + if (isChunkValid(xUpdate, zUpdate)) { + updates.add(new Pos(xUpdate, 0, zUpdate)); + } + } + + final Set<Pos> localLightOpenList = new LinkedHashSet<>(); + for (final Pos pos : updates) { + // initiateLocalLightList(localLightOpenList, pos.x * subMeshSize, pos.z * subMeshSize, pos.x + // * subMeshSize + subMeshSize, pos.z * subMeshSize + subMeshSize, height); + initiateLighting(localLightOpenList, pos.x * subMeshSize, pos.z * subMeshSize, pos.x + * subMeshSize + subMeshSize, pos.z * subMeshSize + subMeshSize, height); + Thread.yield(); + } + + for (final Pos pos : updates) { + fillQueueLocal(localLightDataWrite, localLightOpenList, pos.x * subMeshSize + subMeshSize, + pos.z * subMeshSize, pos.x * subMeshSize + subMeshSize + 1, pos.z * subMeshSize + + subMeshSize, height); + fillQueueLocal(localLightDataWrite, localLightOpenList, pos.x * subMeshSize - 1, pos.z + * subMeshSize, pos.x * subMeshSize, pos.z * subMeshSize + subMeshSize, height); + + fillQueueLocal(localLightDataWrite, localLightOpenList, pos.x * subMeshSize, pos.z + * subMeshSize + subMeshSize, pos.x * subMeshSize + subMeshSize, pos.z * subMeshSize + + subMeshSize + 1, height); + fillQueueLocal(localLightDataWrite, localLightOpenList, pos.x * subMeshSize, pos.z + * subMeshSize - 1, pos.x * subMeshSize + subMeshSize, pos.z * subMeshSize, height); + Thread.yield(); + } + + djikstraLight(localLightDataWrite, localLightOpenList); + Thread.yield(); + + final Set<Pos> openList = new LinkedHashSet<>(); + for (final Pos pos : updates) { + openList.clear(); + floodLighting(openList, pos.x * subMeshSize, pos.z * subMeshSize, pos.x * subMeshSize + + subMeshSize, pos.z * subMeshSize + subMeshSize, height); + + fillQueue(lightingWrite, openList, pos.x * subMeshSize + subMeshSize, pos.z * subMeshSize, + pos.x * subMeshSize + subMeshSize + 1, pos.z * subMeshSize + subMeshSize, height); + fillQueue(lightingWrite, openList, pos.x * subMeshSize - 1, pos.z * subMeshSize, pos.x + * subMeshSize, pos.z * subMeshSize + subMeshSize, height); + + fillQueue(lightingWrite, openList, pos.x * subMeshSize, pos.z * subMeshSize + subMeshSize, + pos.x * subMeshSize + subMeshSize, pos.z * subMeshSize + subMeshSize + 1, height); + fillQueue(lightingWrite, openList, pos.x * subMeshSize, pos.z * subMeshSize - 1, pos.x + * subMeshSize + subMeshSize, pos.z * subMeshSize, height); + Thread.yield(); + + djikstraLight(lightingWrite, openList); + + Thread.yield(); + } + + for (final Pos pos : updates) { + buildLightReadData(lightingWrite, localLightDataWrite, pos.x * subMeshSize, + pos.z * subMeshSize, pos.x * subMeshSize + subMeshSize, pos.z * subMeshSize + + subMeshSize, height); + Thread.yield(); + } + + for (final Pos pos : updates) { + chunkMailBox.add(new LightMessage(pos.x, pos.z)); + } + + try { + Thread.sleep(5); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } else { + try { + Thread.sleep(5); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + } + exitLatch.countDown(); + } + } + + private final class ChunkUpdater implements Runnable { + @Override + public void run() { + final List<UpdateMessage> dataUpdates = new ArrayList<>(); + final List<BlocksMessage> blockDataUpdates = new ArrayList<>(); + final List<UpdateMessage> rebuildUpdates = new ArrayList<>(); + final List<UpdateMessage> lightUpdates = new ArrayList<>(); + + while (!exit) { + final List<UpdateMessage> list = chunkMailBox.switchAndGet(); + + if (!list.isEmpty()) { + dataUpdates.clear(); + blockDataUpdates.clear(); + rebuildUpdates.clear(); + lightUpdates.clear(); + + for (final UpdateMessage updateMessage : list) { + if (updateMessage == null) { + continue; + } + + if (isChunkOutside(updateMessage.chunkX, updateMessage.chunkZ)) { + continue; + } + + if (updateMessage instanceof ChunkMessage) { + dataUpdates.add(updateMessage); + + final RebuildMessage rebuildMessageX = new RebuildMessage(updateMessage.chunkX, + updateMessage.chunkZ); + if (!rebuildUpdates.contains(rebuildMessageX)) { + rebuildUpdates.add(rebuildMessageX); + } + + if (!isChunkOutside(updateMessage.chunkX - 1, updateMessage.chunkZ)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX - 1, + updateMessage.chunkZ); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } + if (!isChunkOutside(updateMessage.chunkX + 1, updateMessage.chunkZ)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX + 1, + updateMessage.chunkZ); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } + if (!isChunkOutside(updateMessage.chunkX, updateMessage.chunkZ - 1)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX, + updateMessage.chunkZ - 1); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } + if (!isChunkOutside(updateMessage.chunkX, updateMessage.chunkZ + 1)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX, + updateMessage.chunkZ + 1); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } + + } else if (updateMessage instanceof BlocksMessage) { + final BlocksMessage message = (BlocksMessage) updateMessage; + + if (blockDataUpdates.contains(message)) { + final BlocksMessage messageOld = blockDataUpdates + .get(blockDataUpdates.indexOf(message)); + messageOld.blockUpdates.addAll(message.blockUpdates); + } else { + blockDataUpdates.add((BlocksMessage) updateMessage); + + final RebuildMessage rebuildMessageX = new RebuildMessage(updateMessage.chunkX, + updateMessage.chunkZ); + if (!rebuildUpdates.contains(rebuildMessageX)) { + rebuildUpdates.add(rebuildMessageX); + } + } + + for (final BlockEditData data : message.blockUpdates) { + addBlockEditDatas(rebuildUpdates, updateMessage, data.pos); + } + } else if (updateMessage instanceof RebuildMessage) { + if (!rebuildUpdates.contains(updateMessage)) { + rebuildUpdates.add(updateMessage); + } + } + } + + for (final UpdateMessage updateMessage : list) { + if (updateMessage instanceof LightMessage) { + if (!rebuildUpdates.contains(updateMessage) && !lightUpdates.contains(updateMessage)) { + lightUpdates.add(updateMessage); + } + } + } + + for (final UpdateMessage updateMessage : dataUpdates) { + if (updateMessage instanceof ChunkMessage) { + final ChunkMessage message = (ChunkMessage) updateMessage; + + WorkerManager.getWorker().enqueue( + new Task(CHUNK, updateMessage.chunkX, updateMessage.chunkZ) { + @Override + public void execute(final Renderer renderer) { + final byte[] data = message.data; + final byte[] extra = message.extra; + + final int x = message.chunkX; + final int z = message.chunkZ; + + if (isChunkOutside(x, z)) { + return; + } + + for (int xx = 0; xx < subMeshSize; xx++) { + for (int yy = 1; yy < height; yy++) { + for (int zz = 0; zz < subMeshSize; zz++) { + final int index = xx + (yy + zz * height) * subMeshSize; + final int blockId = data[index] & 0xff; + final BlockSide orientation = extra == null ? BlockSide.Front + : BlockSide.values()[extra[index] & 0xff]; + + setBlockInternal(xx + x * subMeshSize, yy, + zz + z * subMeshSize, blockId, orientation); + + // TODO: optimize + setLightMax(lightingWrite, xx + x * subMeshSize, yy, zz + z + * subMeshSize); + } + } + } + + // initiateLightingOnly(x * subMeshSize, z * subMeshSize, x * subMeshSize + // + subMeshSize, z * subMeshSize + subMeshSize, height); + + isLoaded.add(new Pos(x, 0, z)); + + lightUpdateBox.add(new Pos(x * subMeshSize, 0, z * subMeshSize)); + lightUpdateBox.add(new Pos(x * subMeshSize + subMeshSize - 1, 0, z + * subMeshSize)); + lightUpdateBox.add(new Pos(x * subMeshSize, 0, z * subMeshSize + + subMeshSize - 1)); + lightUpdateBox.add(new Pos(x * subMeshSize + subMeshSize - 1, 0, z + * subMeshSize + subMeshSize - 1)); + } + + private void setLightMax(final byte[] buffer, int x, int y, int z) { + x = MathUtils.moduloPositive(x, width); + y = MathUtils.clamp(y, 0, height - 1); + z = MathUtils.moduloPositive(z, width); + + buffer[x + (y + z * height) * width] = (byte) 0xff; + } + }); + } + } + + for (final UpdateMessage updateMessage : blockDataUpdates) { + if (updateMessage instanceof BlocksMessage) { + final BlocksMessage message = (BlocksMessage) updateMessage; + + WorkerManager.getWorker().enqueue( + new Task(BLOCKS, updateMessage.chunkX, updateMessage.chunkZ) { + @Override + public void execute(final Renderer renderer) { + final int x = message.chunkX; + final int z = message.chunkZ; + + if (!isChunkValid(x, z)) { + return; + } + + final List<BlockEditData> blockUpdates = message.blockUpdates; + for (final BlockEditData blockEditData : blockUpdates) { + final Pos pos = blockEditData.pos; + setBlockInternal(pos.x, pos.y, pos.z, blockEditData.type, + blockEditData.orientation); + lightUpdateBox.add(pos); + serverConnection.setBlock(pos.x, pos.y, pos.z, blockEditData.type, + blockEditData.orientation); + } + + // initiateLightingOnly(x * subMeshSize, z * subMeshSize, x * subMeshSize + // + subMeshSize, z * subMeshSize + subMeshSize, height); + } + }); + } + } + + for (final UpdateMessage updateMessage : rebuildUpdates) { + if (updateMessage instanceof RebuildMessage) { + WorkerManager.getWorker().enqueue( + new Task(REBUILD, updateMessage.chunkX, updateMessage.chunkZ) { + @Override + public void execute(final Renderer renderer) { + if (!isChunkValid(updateMessage.chunkX, updateMessage.chunkZ)) { + return; + } + + createSubMesh(renderer, updateMessage.chunkX * subMeshSize, 0, + updateMessage.chunkZ * subMeshSize, subMeshSize, height, + subMeshSize, ALL); + } + }); + } + } + + for (final UpdateMessage updateMessage : lightUpdates) { + if (updateMessage instanceof LightMessage) { + WorkerManager.getWorker().enqueue( + new Task(LIGHT, updateMessage.chunkX, updateMessage.chunkZ) { + @Override + public void execute(final Renderer renderer) { + final int x = updateMessage.chunkX; + final int z = updateMessage.chunkZ; + + if (!isChunkValid(x, z)) { + return; + } + + createSubMesh(renderer, x * subMeshSize, 0, z * subMeshSize, subMeshSize, + height, subMeshSize, COLORS); + } + }); + } + } + } + + try { + Thread.sleep(5); + } catch (final InterruptedException e) { + e.printStackTrace(); + } + } + exitLatch.countDown(); + } + + private void addBlockEditDatas(final List<UpdateMessage> rebuildUpdates, final UpdateMessage updateMessage, + final Pos pos) { + final int xx = MathUtils.moduloPositive(pos.x, subMeshSize); + final int zz = MathUtils.moduloPositive(pos.z, subMeshSize); + if (xx == 0 && isChunkValid(updateMessage.chunkX - 1, updateMessage.chunkZ)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX - 1, updateMessage.chunkZ); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } else if (xx == subMeshSize - 1 && isChunkValid(updateMessage.chunkX + 1, updateMessage.chunkZ)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX + 1, updateMessage.chunkZ); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } + if (zz == 0 && isChunkValid(updateMessage.chunkX, updateMessage.chunkZ - 1)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX, updateMessage.chunkZ - 1); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } else if (zz == subMeshSize - 1 && isChunkValid(updateMessage.chunkX, updateMessage.chunkZ + 1)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX, updateMessage.chunkZ + 1); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } + + if (xx == 0 && zz == 0 && isChunkValid(updateMessage.chunkX - 1, updateMessage.chunkZ - 1)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX - 1, + updateMessage.chunkZ - 1); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } else if (xx == subMeshSize - 1 && zz == 0 + && isChunkValid(updateMessage.chunkX + 1, updateMessage.chunkZ - 1)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX + 1, + updateMessage.chunkZ - 1); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } else if (xx == subMeshSize - 1 && zz == subMeshSize - 1 + && isChunkValid(updateMessage.chunkX + 1, updateMessage.chunkZ + 1)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX + 1, + updateMessage.chunkZ + 1); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } else if (xx == 0 && zz == subMeshSize - 1 + && isChunkValid(updateMessage.chunkX - 1, updateMessage.chunkZ + 1)) { + final RebuildMessage rebuildMessage = new RebuildMessage(updateMessage.chunkX - 1, + updateMessage.chunkZ + 1); + if (!rebuildUpdates.contains(rebuildMessage)) { + rebuildUpdates.add(rebuildMessage); + } + } + } + + } + + private boolean isChunkValid(final int chunkX, final int chunkZ) { + if (!isLoaded.contains(new Pos(chunkX, 0, chunkZ))) { + return false; + } + return !isChunkOutside(chunkX, chunkZ); + } + + private boolean isChunkOutside(final int chunkX, final int chunkZ) { + final int diffX1 = playerPositionX - chunkX; + final int diffZ1 = playerPositionZ - chunkZ; + if (diffX1 > gridUnitSize / 2 || diffX1 < -gridUnitSize / 2 + 1 || diffZ1 > gridUnitSize / 2 + || diffZ1 < -gridUnitSize / 2 + 1) { + return true; + } + return false; + } + + private static int VERTICES = 1 << 0; + private static int TEXCOORDS = 1 << 1; + private static int COLORS = 1 << 2; + private static int INDICES = 1 << 3; + private static int ALL = VERTICES | TEXCOORDS | COLORS | INDICES; + + private void createSubMesh(final Renderer renderer, final int xStart, final int yStart, final int zStart, + final int xWidth, final int yWidth, final int zWidth, final int creationType) { + final int modxStart = MathUtils.moduloPositive(xStart, width); + final int modyStart = MathUtils.moduloPositive(yStart, height); + final int modzStart = MathUtils.moduloPositive(zStart, width); + + final Pos pos = new Pos(modxStart / subMeshSize, modyStart / height, modzStart / subMeshSize); + int minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE; + + final float[] vertices = (creationType & VERTICES) == VERTICES ? tmpVertices : null; + final float[] texcoords = (creationType & TEXCOORDS) == TEXCOORDS ? tmpTexcoords : null; + final float[] colors = (creationType & COLORS) == COLORS ? tmpColors : null; + final int[] indices = (creationType & INDICES) == INDICES ? tmpIndices : null; + geometryHandler.setVertices(vertices); + geometryHandler.setTexcoords(texcoords); + geometryHandler.setColors(colors); + geometryHandler.setIndices(indices); + + final float[] verticesTransparent = (creationType & VERTICES) == VERTICES ? tmpVerticesTransparent : null; + final float[] texcoordsTransparent = (creationType & TEXCOORDS) == TEXCOORDS ? tmpTexcoordsTransparent : null; + final float[] colorsTransparent = (creationType & COLORS) == COLORS ? tmpColorsTransparent : null; + final int[] indicesTransparent = (creationType & INDICES) == INDICES ? tmpIndicesTransparent : null; + geometryHandlerTransparent.setVertices(verticesTransparent); + geometryHandlerTransparent.setTexcoords(texcoordsTransparent); + geometryHandlerTransparent.setColors(colorsTransparent); + geometryHandlerTransparent.setIndices(indicesTransparent); + + // long t = System.currentTimeMillis(); + int vertexCount = 0; + int indexCount = 0; + int vertexCountTransparent = 0; + int indexCountTransparent = 0; + + for (int x = xStart; x < xStart + xWidth; x++) { + for (int y = yStart; y < yStart + yWidth; y++) { + for (int z = zStart; z < zStart + zWidth; z++) { + final int block = getBlock(x, y, z); + if (block == 0) { + continue; + } + + if (y < minY) { + minY = y; + } + if (y >= maxY) { + maxY = y; + } + + final boolean isTransparent = blockUtil.getIsSemiTransparent(block); + final GeometryHandler currentGeometryHandler = isTransparent ? geometryHandlerTransparent + : geometryHandler; + + if (!isTransparent) { + currentGeometryHandler.setStartIndexCount(indexCount); + currentGeometryHandler.setStartVertexCount(vertexCount); + } else { + currentGeometryHandler.setStartIndexCount(indexCountTransparent); + currentGeometryHandler.setStartVertexCount(vertexCountTransparent); + } + blockUtil.getGeometryProducer(block).generateBlock(block, currentGeometryHandler, this, blockUtil, + x, y, z); + if (!isTransparent) { + indexCount += currentGeometryHandler.getIndexCount(); + vertexCount += currentGeometryHandler.getVertexCount(); + } else { + indexCountTransparent += currentGeometryHandler.getIndexCount(); + vertexCountTransparent += currentGeometryHandler.getVertexCount(); + } + } + } + } + + setMeshData(renderer, xStart, yStart, zStart, pos, indexCount, vertexCount, vertices, texcoords, colors, + indices, minY, maxY); + setMeshDataTransparent(renderer, xStart, yStart, zStart, pos, indexCountTransparent, vertexCountTransparent, + verticesTransparent, texcoordsTransparent, colorsTransparent, indicesTransparent, minY, maxY); + } + + private void setMeshData(final Renderer renderer, final int xStart, final int yStart, final int zStart, + final Pos pos, final int indexCount, final int vertexCount, final float[] vertices, + final float[] texcoords, final float[] colors, final int[] indices, final int minY, final int maxY) { + if (indexCount == 0) { + if (meshCache.containsKey(pos)) { + final Mesh mesh = meshCache.get(pos); + if (solidNode.hasChild(mesh)) { + solidNode.detachChild(mesh); + } + } + return; + } + + Mesh mesh; + if (meshCache.containsKey(pos)) { + mesh = meshCache.get(pos); + if (!solidNode.hasChild(mesh)) { + solidNode.attachChild(mesh); + } + } else { + if (indices == null) { + return; + } + + mesh = new Mesh(xStart + "," + yStart + "," + zStart); + mesh.getSceneHints().setLightCombineMode(LightCombineMode.Off); + + mesh.setModelBound(new BoundingBox(new Vector3(), subMeshSize / 2, height / 2, subMeshSize / 2)); + mesh.getSceneHints().setCullHint(CullHint.Dynamic); + + mesh.setRenderState(terrainTextureState); + mesh.setRenderState(cullState); + mesh.setRenderState(terrainBlendState); + + if (hasVBOSupport) { + mesh.getSceneHints().setDataMode(DataMode.VBO); + } + + solidNode.attachChild(mesh); + meshCache.put(pos, mesh); + mesh.getMeshData().setIndexMode(IndexMode.Triangles); + } + + updateBuffers(renderer, xStart, zStart, indexCount, vertexCount, vertices, texcoords, colors, indices, minY, + maxY, mesh); + } + + private void setMeshDataTransparent(final Renderer renderer, final int xStart, final int yStart, final int zStart, + final Pos pos, final int indexCount, final int vertexCount, final float[] vertices, + final float[] texcoords, final float[] colors, final int[] indices, final int minY, final int maxY) { + if (indexCount == 0) { + if (meshCacheTransparent.containsKey(pos)) { + final Mesh mesh = meshCacheTransparent.get(pos); + if (transparentNode.hasChild(mesh)) { + transparentNode.detachChild(mesh); + } + } + return; + } + + Mesh mesh; + if (meshCacheTransparent.containsKey(pos)) { + mesh = meshCacheTransparent.get(pos); + if (!transparentNode.hasChild(mesh)) { + transparentNode.attachChild(mesh); + } + } else { + if (indices == null) { + return; + } + + mesh = new Mesh(xStart + "," + yStart + "," + zStart); + mesh.getSceneHints().setLightCombineMode(LightCombineMode.Off); + + mesh.setModelBound(new BoundingBox(new Vector3(), subMeshSize / 2, height / 2, subMeshSize / 2)); + mesh.getSceneHints().setCullHint(CullHint.Dynamic); + + mesh.setRenderState(terrainTextureState); + mesh.setRenderState(windowCullState); + mesh.setRenderState(transparentState); + + if (hasVBOSupport) { + mesh.getSceneHints().setDataMode(DataMode.VBO); + } + + transparentNode.attachChild(mesh); + meshCacheTransparent.put(pos, mesh); + mesh.getMeshData().setIndexMode(IndexMode.Triangles); + } + + updateBuffers(renderer, xStart, zStart, indexCount, vertexCount, vertices, texcoords, colors, indices, minY, + maxY, mesh); + } + + private void updateBuffers(final Renderer renderer, final int xStart, final int zStart, final int indexCount, + final int vertexCount, final float[] vertices, final float[] texcoords, final float[] colors, + final int[] indices, final int minY, final int maxY, final Mesh mesh) { + final int height = (maxY - minY) / 2 + 1; + final int posY = (maxY + minY) / 2 + 1; + final BoundingBox boundingBox = (BoundingBox) mesh.getModelBound(); + boundingBox.setYExtent(height); + boundingBox.setCenter(xStart + subMeshSize / 2, posY, zStart + subMeshSize / 2); + mesh.updateWorldBound(false); + + final MeshData meshData = mesh.getMeshData(); + + if (vertices != null) { + FloatBuffer vertexBuffer = meshData.getVertexBuffer(); + if (vertexBuffer != null && vertexBuffer.capacity() >= vertexCount * 3) { + vertexBuffer.clear(); + if (hasVBOSupport) { + meshData.getVertexCoords().setNeedsRefresh(true); + // meshData.getVertexCoords().removeVBOID(ContextManager.getCurrentContext().getGlContextRep()); + } + } else { + if (hasVBOSupport && vertexBuffer != null) { + renderer.deleteVBOs(meshData.getVertexCoords()); + } + vertexBuffer = BufferUtils.createFloatBuffer(vertexCount * 3); + meshData.setVertexBuffer(vertexBuffer); + if (hasVBOSupport) { + meshData.getVertexCoords().setVboAccessMode(VBOAccessMode.DynamicDraw); + } + } + vertexBuffer.put(vertices, 0, vertexCount * 3); + } + + if (texcoords != null) { + FloatBuffer textureBuffer = meshData.getTextureBuffer(0); + if (textureBuffer != null && textureBuffer.capacity() >= vertexCount * 2) { + textureBuffer.clear(); + if (hasVBOSupport) { + meshData.getTextureCoords(0).setNeedsRefresh(true); + // meshData.getTextureCoords(0).removeVBOID(ContextManager.getCurrentContext().getGlContextRep()); + } + } else { + if (hasVBOSupport && textureBuffer != null) { + renderer.deleteVBOs(meshData.getTextureCoords(0)); + } + textureBuffer = BufferUtils.createFloatBuffer(vertexCount * 2); + meshData.setTextureBuffer(textureBuffer, 0); + if (hasVBOSupport) { + meshData.getTextureCoords(0).setVboAccessMode(VBOAccessMode.DynamicDraw); + } + } + textureBuffer.put(texcoords, 0, vertexCount * 2); + } + + if (colors != null) { + FloatBuffer colorBuffer = meshData.getTextureBuffer(1); + if (colorBuffer != null && colorBuffer.capacity() >= vertexCount * 2) { + colorBuffer.clear(); + if (hasVBOSupport) { + meshData.getTextureCoords(1).setNeedsRefresh(true); + // meshData.getColorCoords().removeVBOID(ContextManager.getCurrentContext().getGlContextRep()); + } + } else { + if (hasVBOSupport && colorBuffer != null) { + renderer.deleteVBOs(meshData.getTextureCoords(1)); + } + colorBuffer = BufferUtils.createFloatBuffer(vertexCount * 2); + meshData.setTextureBuffer(colorBuffer, 1); + if (hasVBOSupport) { + meshData.getTextureCoords(1).setVboAccessMode(VBOAccessMode.DynamicDraw); + } + } + colorBuffer.put(colors, 0, vertexCount * 2); + } + + if (indices != null) { + IntBuffer indexBuffer = (IntBuffer) meshData.getIndexBuffer(); + if (indexBuffer != null && indexBuffer.capacity() >= indexCount) { + final int oldLimit = indexBuffer.limit(); + indexBuffer.clear(); + indexBuffer.limit(indexCount); + if (hasVBOSupport) { + if (indexCount <= oldLimit) { + meshData.getIndices().setNeedsRefresh(true); + } else { + renderer.deleteVBOs(meshData.getIndices()); + } + } + } else { + if (hasVBOSupport && indexBuffer != null) { + renderer.deleteVBOs(meshData.getIndices()); + } + indexBuffer = BufferUtils.createIntBuffer(indexCount); + meshData.setIndexBuffer(indexBuffer); + if (hasVBOSupport) { + meshData.getIndices().setVboAccessMode(VBOAccessMode.DynamicDraw); + } + } + indexBuffer.put(indices, 0, indexCount); + } + } + + /** + * Get the exact float used global lighting. + * + * @param lighting + * @return float + */ + public float lookupLighting(final int lighting) { + final int clampedLight = MathUtils.clamp(lighting, 0, 15); + return clampedLight / 15f; + } + + @Override + public int getBlock(int x, final int y, int z) { + if (testOutsideBounds(x, y, z)) { + return 1; + } + + x = MathUtils.moduloPositive(x, width); + z = MathUtils.moduloPositive(z, width); + + return blocks[x + (y + z * height) * width] & 0xff; + } + + private int getBlockFast(final int x, final int y, final int z) { + return blocks[x + (y + z * height) * width] & 0xff; + } + + @Override + public void setBlock(final int x, final int y, final int z, final int blockId) { + setBlock(x, y, z, blockId, BlockSide.Front); + } + + @Override + public void setBlock(final int x, final int y, final int z, final int blockId, final BlockSide orientation) { + if (testOutsideBounds(x, y, z)) { + return; + } + + final BlockEditData data = new BlockEditData(new Pos(x, y, z), blockId, orientation); + + final int chunkX = MathUtils.floor((float) x / subMeshSize); + final int chunkZ = MathUtils.floor((float) z / subMeshSize); + chunkMailBox.add(new BlocksMessage(chunkX, chunkZ, data)); + } + + @Override + public void setBlocks(final List<BlockEditData> blockList) { + final Map<Coords, List<BlockEditData>> chunks = new HashMap<>(); + for (final BlockEditData data : blockList) { + final int xx = data.pos.x; + final int zz = data.pos.z; + final int chunkX = MathUtils.floor((float) xx / subMeshSize); + final int chunkZ = MathUtils.floor((float) zz / subMeshSize); + final Coords coord = new Coords(); + coord.setX(chunkX); + coord.setZ(chunkZ); + + List<BlockEditData> subData = chunks.get(coord); + if (subData == null) { + subData = new ArrayList<>(); + chunks.put(coord, subData); + } + subData.add(data); + } + for (final Entry<Coords, List<BlockEditData>> entry : chunks.entrySet()) { + final Coords coord = entry.getKey(); + final List<BlockEditData> list = entry.getValue(); + chunkMailBox.add(new BlocksMessage(coord.getX(), coord.getZ(), list)); + } + } + + /** + * Get block orientation at coordinate x, y, z. + * + * @param x + * @param y + * @param z + * @return Orientation + */ + public BlockSide getBlockOrientation(final int x, final int y, final int z) { + return BlockSide.values()[getBlockExtra(x, y, z)]; + } + + @Override + public int getBlockExtra(int x, final int y, int z) { + if (testOutsideBounds(x, y, z)) { + return 0; + } + + x = MathUtils.moduloPositive(x, width); + z = MathUtils.moduloPositive(z, width); + + return blockExtra[x + (y + z * height) * width] & 0x7; + } + + private void setBlockInternal(final int xx, final int yy, final int zz, final int data, final BlockSide orientation) { + if (testOutsideBounds(xx, yy, zz)) { + return; + } + + final int x = MathUtils.moduloPositive(xx, width); + final int y = yy; + final int z = MathUtils.moduloPositive(zz, width); + + blocks[x + (y + z * height) * width] = (byte) data; + blockExtra[x + (y + z * height) * width] = (byte) orientation.ordinal(); + } + + private void setLightSolid(int x, final int y, int z, final boolean val) { + x = MathUtils.moduloPositive(x, width); + z = MathUtils.moduloPositive(z, width); + isLightSolid.set(x + (y + z * height) * width, val); + } + + private boolean isLightSolid(int x, final int y, int z) { + x = MathUtils.moduloPositive(x, width); + z = MathUtils.moduloPositive(z, width); + return isLightSolid.get(x + (y + z * height) * width); + } + + private boolean testOutsideBounds(final int x, final int y, final int z) { + return y < 1 || y >= height || x < currentTileX - gridSize || x >= currentTileX + gridSize + || z < currentTileZ - gridSize || z >= currentTileZ + gridSize; + } + + private byte getLight(final byte[] buffer, int x, int y, int z) { + x = MathUtils.moduloPositive(x, width); + y = MathUtils.clamp(y, 0, height - 1); + z = MathUtils.moduloPositive(z, width); + + return (byte) (buffer[x + (y + z * height) * width] & 0x0f); + } + + private void setLight(final byte[] buffer, int x, int y, int z, final int data) { + x = MathUtils.moduloPositive(x, width); + y = MathUtils.clamp(y, 0, height - 1); + z = MathUtils.moduloPositive(z, width); + + buffer[x + (y + z * height) * width] = (byte) (buffer[x + (y + z * height) * width] & 0xf0 | data & 0xf); + } + + private byte getLightRead(final byte[] buffer, int x, int y, int z) { + x = MathUtils.moduloPositive(x, width); + y = MathUtils.clamp(y, 0, height - 1); + z = MathUtils.moduloPositive(z, width); + + return (byte) (buffer[x + (y + z * height) * width] >> 4 & 0xf); + } + + private void setLightRead(final byte[] buffer, int x, int y, int z, final int data) { + x = MathUtils.moduloPositive(x, width); + y = MathUtils.clamp(y, 0, height - 1); + z = MathUtils.moduloPositive(z, width); + + buffer[x + (y + z * height) * width] = (byte) (buffer[x + (y + z * height) * width] & 0x0f | (data & 0xf) << 4); + } + + private int getLightHeightmap(int x, int z) { + if (testOutsideBounds(x, 5, z)) { + return height; + } + + x = MathUtils.moduloPositive(x, width); + z = MathUtils.moduloPositive(z, width); + + return lightHeightmap[x + z * width]; + } + + private void setLightHeightmap(final int x, final int z, final short data) { + lightHeightmap[x + z * width] = data; + } + + private void initiateLighting(final Set<Pos> localLightOpenList, final int xStart, final int zStart, + final int xEnd, final int zEnd, final int height) { + for (int x = xStart; x < xEnd; x++) { + for (int z = zStart; z < zEnd; z++) { + final int modx = MathUtils.moduloPositive(x, width); + final int modz = MathUtils.moduloPositive(z, width); + + int currentLight = MAX_LIGHT; + for (int y = height - 1; y >= 0; y--) { + final int block = getBlockFast(modx, y, modz); + final boolean solid = blockUtil.getBlockType(block) == BlockType.Solid; + setLightSolid(x, y, z, false); + if (currentLight > 0) { + if (block != 0 && block != WATER && solid) { + setLightHeightmap(modx, modz, (short) y); + setLight(lightingWrite, x, y, z, 0); + setLight(localLightDataWrite, x, y, z, 0); + setLightSolid(x, y, z, true); + currentLight = 0; + } else { + setLight(lightingWrite, x, y, z, currentLight); + setLight(localLightDataWrite, x, y, z, 0); + } + if (block == WATER) { + currentLight -= 2; + if (currentLight < 0) { + currentLight = 0; + } + } else if (block != 0 && !solid) { + currentLight -= 1; + if (currentLight < 0) { + currentLight = 0; + } + } + } else if (block == 0 || block == WATER || !solid) { + setLight(lightingWrite, x, y, z, 0); + setLight(localLightDataWrite, x, y, z, 0); + } else { + setLight(lightingWrite, x, y, z, 0); + setLight(localLightDataWrite, x, y, z, 0); + setLightSolid(x, y, z, true); + } + + if (blockUtil.isLocalLight(block)) { + setLight(localLightDataWrite, x, y, z, MAX_LIGHT); + localLightOpenList.add(new Pos(x, y, z)); + } + } + } + } + } + +// private void initiateLocalLightList(final Set<Pos> localLightOpenList, final int xStart, final int zStart, +// final int xEnd, final int zEnd, final int height) { +// for (int x = xStart; x < xEnd; x++) { +// for (int z = zStart; z < zEnd; z++) { +// final int modx = MathUtils.moduloPositive(x, width); +// final int modz = MathUtils.moduloPositive(z, width); +// +// for (int y = height - 1; y >= 0; y--) { +// final int block = getBlockFast(modx, y, modz); +// if (blockUtil.isLocalLight(block)) { +// localLightOpenList.add(new Pos(x, y, z)); +// } +// } +// } +// } +// } + +// private void initiateLightingOnly(final int xStart, final int zStart, final int xEnd, final int zEnd, +// final int height) { +// for (int x = xStart; x < xEnd; x++) { +// for (int z = zStart; z < zEnd; z++) { +// final int modx = MathUtils.moduloPositive(x, width); +// final int modz = MathUtils.moduloPositive(z, width); +// +// int currentLight = MAX_LIGHT; +// for (int y = height - 1; y >= 0; y--) { +// final int block = getBlockFast(modx, y, modz); +// final boolean solid = blockUtil.getBlockType(block) == BlockType.Solid; +// setLightSolid(x, y, z, false); +// if (currentLight > 0) { +// if (block != 0 && block != WATER && solid) { +// setLightHeightmap(modx, modz, (short) y); +// setLight(lightingWrite, x, y, z, 0); +// setLight(localLightDataWrite, x, y, z, 0); +// setLightSolid(x, y, z, true); +// currentLight = 0; +// } else { +// setLight(lightingWrite, x, y, z, currentLight); +// setLight(localLightDataWrite, x, y, z, 0); +// } +// if (block == WATER) { +// currentLight -= 2; +// if (currentLight < 0) { +// currentLight = 0; +// } +// } else if (block != 0 && !solid) { +// currentLight -= 1; +// if (currentLight < 0) { +// currentLight = 0; +// } +// } +// } else if (block == 0 || block == WATER || !solid) { +// setLight(lightingWrite, x, y, z, 0); +// setLight(localLightDataWrite, x, y, z, 0); +// } else { +// setLight(lightingWrite, x, y, z, 0); +// setLight(localLightDataWrite, x, y, z, 0); +// setLightSolid(x, y, z, true); +// } +// } +// } +// } +// } + + private void fillQueue(final byte[] write, final Set<Pos> openList, final int xStart, final int zStart, + final int xEnd, final int zEnd, final int height) { + if (testOutsideBounds(xStart, 5, zStart)) { + return; + } + for (int x = xStart; x < xEnd; x++) { + for (int z = zStart; z < zEnd; z++) { + final int yStart = getLightHeightmap(x, z) - 1; + for (int y = yStart; y >= 0; y--) { + final byte currentLight = getLight(write, x, y, z); + if (currentLight <= LIGHT_STEP) { + continue; + } + addToLightQueue(openList, x, y, z); + } + } + } + } + + private void fillQueueLocal(final byte[] write, final Set<Pos> openList, final int xStart, final int zStart, + final int xEnd, final int zEnd, final int height) { + if (testOutsideBounds(xStart, 5, zStart)) { + return; + } + for (int x = xStart; x < xEnd; x++) { + for (int z = zStart; z < zEnd; z++) { + for (int y = height - 1; y >= 0; y--) { + final byte currentLight = getLight(write, x, y, z); + if (currentLight <= LIGHT_STEP) { + continue; + } + addToLightQueue(openList, x, y, z); + } + } + } + } + + private void floodLighting(final Set<Pos> openList, final int xStart, final int zStart, final int xEnd, + final int zEnd, final int height) { + for (int x = xStart; x < xEnd; x++) { + for (int z = zStart; z < zEnd; z++) { + final int yStart = getLightHeightmap(x, z) - 1; + + if (testOutsideBounds(x, 5, z)) { + continue; + } + final int modx = MathUtils.moduloPositive(x, width); + final int modz = MathUtils.moduloPositive(z, width); + + for (int y = yStart; y >= 0; y--) { + final int block = getBlockFast(modx, y, modz); + final boolean solid = blockUtil.getBlockType(block) == BlockType.Solid; + if (block != 0 && solid) { + continue; + } + + int neighbourHeight; + + // if (!testOutsideBounds(x + 1, 5, z)) { + neighbourHeight = getLightHeightmap(x + 1, z); + if (neighbourHeight < y) { + affectLightNode(lightingWrite, openList, x, y, z, getLight(lightingWrite, x + 1, y, z)); + continue; + } + // } + // if (!testOutsideBounds(x - 1, 5, z)) { + neighbourHeight = getLightHeightmap(x - 1, z); + if (neighbourHeight < y) { + affectLightNode(lightingWrite, openList, x, y, z, getLight(lightingWrite, x - 1, y, z)); + continue; + } + // } + // if (!testOutsideBounds(x, 5, z + 1)) { + neighbourHeight = getLightHeightmap(x, z + 1); + if (neighbourHeight < y) { + affectLightNode(lightingWrite, openList, x, y, z, getLight(lightingWrite, x, y, z + 1)); + continue; + } + // } + // if (!testOutsideBounds(x, 5, z - 1)) { + neighbourHeight = getLightHeightmap(x, z - 1); + if (neighbourHeight < y) { + affectLightNode(lightingWrite, openList, x, y, z, getLight(lightingWrite, x, y, z - 1)); + continue; + } + // } + } + } + } + } + + private void addToLightQueue(final Set<Pos> openList, final int x, final int y, final int z) { + openList.add(new Pos(x, y, z)); + } + + private void djikstraLight(final byte[] write, Set<Pos> openList) { + int iterations = 0; + Set<Pos> backupList = new LinkedHashSet<>(); + while (!openList.isEmpty()) { + final Iterator<Pos> iterator = openList.iterator(); + while (iterator.hasNext()) { + final Pos openPos = iterator.next(); + iterator.remove(); + + final int x = openPos.x; + final int y = openPos.y; + final int z = openPos.z; + + final byte currentLight = getLight(write, x, y, z); + if (currentLight <= LIGHT_STEP) { + continue; + } + + if (testLightExpand(write, x - 1, y, z, currentLight)) { + affectLightNode(write, backupList, x - 1, y, z, currentLight); + } + if (testLightExpand(write, x + 1, y, z, currentLight)) { + affectLightNode(write, backupList, x + 1, y, z, currentLight); + } + if (testLightExpand(write, x, y - 1, z, currentLight)) { + affectLightNode(write, backupList, x, y - 1, z, currentLight); + } + if (testLightExpand(write, x, y + 1, z, currentLight)) { + affectLightNode(write, backupList, x, y + 1, z, currentLight); + } + if (testLightExpand(write, x, y, z - 1, currentLight)) { + affectLightNode(write, backupList, x, y, z - 1, currentLight); + } + if (testLightExpand(write, x, y, z + 1, currentLight)) { + affectLightNode(write, backupList, x, y, z + 1, currentLight); + } + + if (iterations++ > 50000) { + logger.warning("Too many light calcs, skipping: " + iterations + ", " + openList.size()); + return; + } + if (iterations == 10000) { + logger.warning("Lots of light calcs: " + iterations + ", " + openList.size()); + } + if (iterations == 30000) { + logger.warning("Scary amount of light calcs: " + iterations + ", " + openList.size()); + } + } + final Set<Pos> tmp = openList; + openList = backupList; + backupList = tmp; + } + } + + private void affectLightNode(final byte[] write, final Set<Pos> openList, final int x, final int y, final int z, + final byte currentLight) { + addToLightQueue(openList, x, y, z); + final int light = currentLight - LIGHT_STEP; + setLight(write, x, y, z, light < 0 ? 0 : light); + } + + private boolean testLightExpand(final byte[] write, final int x, final int y, final int z, final byte currentLight) { + if (testOutsideBounds(x, y, z)) { + return false; + } + + final int block = getBlock(x, y, z); + final boolean solid = blockUtil.getBlockType(block) == BlockType.Solid; + + return (block == 0 || block == WATER || !solid) && getLight(write, x, y, z) < currentLight - LIGHT_STEP; + } + + private void buildLightReadData(final byte[] writeGlobal, final byte[] writeLocal, final int xStart, + final int zStart, final int xEnd, final int zEnd, final int height) { + for (int x = xStart; x < xEnd + 1; x++) { + for (int y = 0; y < height; y++) { + for (int z = zStart; z < zEnd + 1; z++) { + float dataGlobal = 0; + float dataLocal = 0; + float divider = 0; + + if (!isLightSolid(x, y, z)) { + dataGlobal += getLight(writeGlobal, x, y, z); + dataLocal += getLight(writeLocal, x, y, z); + divider++; + } + if (!isLightSolid(x - 1, y, z)) { + dataGlobal += getLight(writeGlobal, x - 1, y, z); + dataLocal += getLight(writeLocal, x - 1, y, z); + divider++; + } + if (!isLightSolid(x, y, z - 1)) { + dataGlobal += getLight(writeGlobal, x, y, z - 1); + dataLocal += getLight(writeLocal, x, y, z - 1); + divider++; + } + if (!isLightSolid(x - 1, y, z - 1)) { + dataGlobal += getLight(writeGlobal, x - 1, y, z - 1); + dataLocal += getLight(writeLocal, x - 1, y, z - 1); + divider++; + } + if (y > 0) { + if (!isLightSolid(x, y - 1, z)) { + dataGlobal += getLight(writeGlobal, x, y - 1, z); + dataLocal += getLight(writeLocal, x, y - 1, z); + divider++; + } + if (!isLightSolid(x - 1, y - 1, z)) { + dataGlobal += getLight(writeGlobal, x - 1, y - 1, z); + dataLocal += getLight(writeLocal, x - 1, y - 1, z); + divider++; + } + if (!isLightSolid(x, y - 1, z - 1)) { + dataGlobal += getLight(writeGlobal, x, y - 1, z - 1); + dataLocal += getLight(writeLocal, x, y - 1, z - 1); + divider++; + } + if (!isLightSolid(x - 1, y - 1, z - 1)) { + dataGlobal += getLight(writeGlobal, x - 1, y - 1, z - 1); + dataLocal += getLight(writeLocal, x - 1, y - 1, z - 1); + divider++; + } + } + + if (divider > 0) { + setLightRead(writeGlobal, x, y, z, (int) (dataGlobal / divider)); + setLightRead(writeLocal, x, y, z, (int) (dataLocal / divider)); + } else { + setLightRead(writeGlobal, x, y, z, 0); + setLightRead(writeLocal, x, y, z, 0); + } + } + } + } + } + + public int getHeight() { + return height; + } + + private final HitTester defaultPickingTester = new HitTester() { + @Override + public boolean isHit(final int blockId) { + return blockUtil.getIsPickable(blockId); + } + }; + private final HitTester defaultHitTester = new HitTester() { + @Override + public boolean isHit(final int blockId) { + return blockUtil.getIsCollidable(blockId); + } + }; + + public void tracePicking(final ReadOnlyVector3 curpos, final ReadOnlyVector3 raydir, final int maxIterations, + final IntersectionResult result) { + new Tracer(this, defaultPickingTester, height).traceCollision(curpos, raydir, maxIterations, result); + } + + public void traceCollision(final ReadOnlyVector3 curpos, final ReadOnlyVector3 raydir, final int maxIterations, + final IntersectionResult result) { + new Tracer(this, defaultHitTester, height).traceCollision(curpos, raydir, maxIterations, result); + } + + /** + * Stop all processing threads, and wait for them to finish. + */ + public void stopThreads() { + if (threadsStarted) { + exit = true; + + logger.info("Stopping threads..."); + + boolean wait = false; + try { + wait = exitLatch.await(20, TimeUnit.SECONDS); + } catch (final InterruptedException e1) { + e1.printStackTrace(); + } + + chunkMailBox.switchAndGet(); + chunkMailBox.switchAndGet(); + lightUpdateBox.switchAndGet(); + lightUpdateBox.switchAndGet(); + + threadsStarted = false; + + logger.info("All threads done: " + wait); + } + } + + public float getGlobalLight() { + return globalLight; + } + + /** + * Set global light value (0-15) for day/night simulation. + * + * @param globalLight + */ + public void setGlobalLight(final float globalLight) { + this.globalLight = MathUtils.clamp(globalLight, 0f, 1f); + updateLighting = true; + } + + public WorldSettings getSettings() { + return settings; + } + + public BlockUtil getBlockUtil() { + return blockUtil; + } + + @Override + public float getGlobalLighting(final int x, final int y, final int z) { + final int lighting = getLightRead(lightingWrite, x, y, z); + return lookupLighting(lighting); + } + + @Override + public float getLocalLighting(final int x, final int y, final int z) { + final int lighting = getLightRead(localLightDataWrite, x, y, z); + return lookupLighting(lighting); + } + + public Texture getLightTexture() { + return lightTexture; + } + + public void setLightTexture(final Texture lightTexture) { + this.lightTexture = lightTexture; + } + + public ReadOnlyColorRGBA getCaveLight() { + return caveLight; + } + + public void setCaveLight(final ColorRGBA caveLight) { + this.caveLight.set(caveLight); + updateLighting = true; + } + + public ReadOnlyColorRGBA getNightLight() { + return nightLight; + } + + public void setNightLight(final ColorRGBA nightLight) { + this.nightLight.set(nightLight); + updateLighting = true; + } + + public ReadOnlyColorRGBA getDayLight() { + return dayLight; + } + + public void setDayLight(final ColorRGBA dayLight) { + this.dayLight.set(dayLight); + updateLighting = true; + } + + public ReadOnlyColorRGBA getTorchLight() { + return torchLight; + } + + public void setTorchLight(final ColorRGBA torchLight) { + this.torchLight.set(torchLight); + updateLighting = true; + } + + public int getCurrentlyLoadedChunksCount() { + return isLoaded.size(); + } + + private abstract class UpdateMessage { + int chunkX; + int chunkZ; + + @Override + public String toString() { + return "UpdateMessage [chunkX=" + chunkX + ", chunkZ=" + chunkZ + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + chunkX; + result = prime * result + chunkZ; + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final UpdateMessage other = (UpdateMessage) obj; + if (chunkX != other.chunkX) { + return false; + } + if (chunkZ != other.chunkZ) { + return false; + } + return true; + } + } + + private class BlocksMessage extends UpdateMessage { + List<BlockEditData> blockUpdates; + + public BlocksMessage(final int chunkX, final int chunkZ, final List<BlockEditData> blockUpdates) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.blockUpdates = blockUpdates; + } + + public BlocksMessage(final int chunkX, final int chunkZ, final BlockEditData singleBlockUpdate) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + blockUpdates = new ArrayList<>(); + blockUpdates.add(singleBlockUpdate); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("BlocksMessage [chunkX="); + builder.append(chunkX); + builder.append(", chunkZ="); + builder.append(chunkZ); + builder.append(", blockUpdates="); + builder.append(blockUpdates); + builder.append("]"); + return builder.toString(); + } + } + + private class ChunkMessage extends UpdateMessage { + byte[] data; + byte[] extra; + + public ChunkMessage(final int chunkX, final int chunkZ, final byte[] data, final byte[] extra) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + this.data = data; + this.extra = extra; + } + } + + private class LightMessage extends UpdateMessage { + public LightMessage(final int chunkX, final int chunkZ) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + } + } + + private class RebuildMessage extends UpdateMessage { + public RebuildMessage(final int chunkX, final int chunkZ) { + this.chunkX = chunkX; + this.chunkZ = chunkZ; + } + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/Chunk.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/Chunk.java new file mode 100644 index 0000000..bca52a2 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/Chunk.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +public class Chunk { + private final byte[] blocks; + private final byte[] extra; + + public Chunk(final byte[] blocks, final byte[] extra) { + this.blocks = blocks; + this.extra = extra; + } + + public byte[] getBlocks() { + return blocks; + } + + public byte[] getExtra() { + return extra; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/ChunkModifier.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/ChunkModifier.java new file mode 100644 index 0000000..4ed268d --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/ChunkModifier.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +import java.util.List; + +/** + * Interface for classes that allows block editing. + */ +public interface ChunkModifier extends BlockProvider { + void postChunk(final int x, final int z, final Chunk chunk); + + void setBlock(final int x, final int y, final int z, final int blockId); + + void setBlock(final int x, final int y, final int z, final int blockId, final BlockSide orientation); + + void setBlocks(final List<BlockEditData> blockList); +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/GeometryHandler.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/GeometryHandler.java new file mode 100644 index 0000000..0738fde --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/GeometryHandler.java @@ -0,0 +1,217 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +import com.ardorcraft.util.geometryproducers.GeometryProducer; + +/** + * This is the class that {@link GeometryProducer}s use to set geometry into the world. + */ +public final class GeometryHandler { + private float[] vertices; + private float[] texcoords; + private float[] colors; + private int vertexCount; + private int startVertexCount; + private int[] indices; + private int indexCount; + private int startIndexCount; + + private final LightProvider lightProvider; + + GeometryHandler(final LightProvider lightProvider) { + this.lightProvider = lightProvider; + } + + void setStartIndexCount(final int index) { + startIndexCount = index; + } + + void setStartVertexCount(final int index) { + startVertexCount = index; + } + + /** + * Sets the index count. + * + * @param count + * the new index count + */ + public void setIndexCount(final int count) { + indexCount = count; + } + + /** + * Sets the vertex count. + * + * @param count + * the new vertex count + */ + public void setVertexCount(final int count) { + vertexCount = count; + } + + /** + * Gets the index count. + * + * @return the index count + */ + public int getIndexCount() { + return indexCount; + } + + /** + * Gets the vertex count. + * + * @return the vertex count + */ + public int getVertexCount() { + return vertexCount; + } + + /** + * Checks for vertices. + * + * @return true, if successful + */ + public boolean hasVertices() { + return vertices != null; + } + + /** + * Sets the vertex. + * + * @param index + * the index + * @param x + * the x + * @param y + * the y + * @param z + * the z + */ + public void setVertex(final int index, final float x, final float y, final float z) { + vertices[startVertexCount * 3 + index * 3 + 0] = x; + vertices[startVertexCount * 3 + index * 3 + 1] = y; + vertices[startVertexCount * 3 + index * 3 + 2] = z; + } + + /** + * Checks for texture coords. + * + * @return true, if successful + */ + public boolean hasTextureCoords() { + return texcoords != null; + } + + /** + * Sets the texture coord. + * + * @param index + * the index + * @param u + * the u + * @param v + * the v + */ + public void setTextureCoord(final int index, final float u, final float v) { + texcoords[startVertexCount * 2 + index * 2 + 0] = u; + texcoords[startVertexCount * 2 + index * 2 + 1] = v; + } + + /** + * Checks for colors. + * + * @return true, if successful + */ + public boolean hasColors() { + return colors != null; + } + + /** + * Sets the color. + * + * @param index + * the index + * @param globalLight + * the global light + * @param localLight + * the local light + */ + public void setColor(final int index, final float globalLight, final float localLight) { + colors[startVertexCount * 2 + index * 2 + 0] = globalLight; + colors[startVertexCount * 2 + index * 2 + 1] = localLight; + } + + /** + * Checks for indices. + * + * @return true, if successful + */ + public boolean hasIndices() { + return indices != null; + } + + /** + * Sets the index. + * + * @param index + * the index + * @param value + * the value + */ + public void setIndex(final int index, final int value) { + indices[startIndexCount + index] = startVertexCount + value; + } + + /** + * Gets the global lighting. + * + * @param x + * the x + * @param y + * the y + * @param z + * the z + * @return the global lighting + */ + public float getGlobalLighting(final int x, final int y, final int z) { + return lightProvider.getGlobalLighting(x, y, z); + } + + /** + * Gets the local lighting. + * + * @param x + * the x + * @param y + * the y + * @param z + * the z + * @return the local lighting + */ + public float getLocalLighting(final int x, final int y, final int z) { + return lightProvider.getLocalLighting(x, y, z); + } + + void setVertices(final float[] vertices) { + this.vertices = vertices; + } + + void setTexcoords(final float[] texcoords) { + this.texcoords = texcoords; + } + + void setColors(final float[] colors) { + this.colors = colors; + } + + void setIndices(final int[] indices) { + this.indices = indices; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/IServerConnection.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/IServerConnection.java new file mode 100644 index 0000000..cda7c25 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/IServerConnection.java @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +import com.ardor3d.math.type.ReadOnlyVector3; + +public interface IServerConnection { + void getModifier(ChunkModifier chunkModifier); + + void update(final int x, final int z); + + void requestChunk(final int x, final int z); + + void setBlock(final int x, final int y, final int z, final int blockId, final BlockSide orientation); + + void updatePlayerPosition(final ReadOnlyVector3 location, ReadOnlyVector3 direction); + + void connect(final String address); + + void close(); +}
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/LightProvider.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/LightProvider.java new file mode 100644 index 0000000..097b64e --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/LightProvider.java @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +public interface LightProvider { + float getGlobalLighting(final int x, final int y, final int z); + + float getLocalLighting(final int x, final int y, final int z); +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldModifier.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldModifier.java new file mode 100644 index 0000000..7e58be1 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldModifier.java @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +public interface WorldModifier { + + void setBlock(final int x, final int y, final int z, final int data); + + int getBlock(final int x, final int y, final int z); + +}
\ No newline at end of file diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldSettings.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldSettings.java new file mode 100644 index 0000000..6f83e4a --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/WorldSettings.java @@ -0,0 +1,227 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world; + +import com.ardor3d.math.MathUtils; +import com.ardor3d.util.resource.ResourceSource; + +/** + * Settings class for setting up the BlockWorld object. + * <p> + * terrainTexture, terrainTextureTileSize, waterTexture and serverConnection has to be setup. + */ +public class WorldSettings { + private int tileSize = 16; + private int tileHeight = 128; + private int gridSize = 16; + + private ResourceSource terrainTexture; + private int terrainTextureTileSize; + private ResourceSource waterTexture; + + private boolean useVBO = true; + private boolean doDefaultTint = true; + + private IServerConnection serverConnection; + + public WorldSettings() { + + } + + /** + * Copy constructor for WorldSettings. + * + * @param settings + * WorldSettings to copy + */ + public WorldSettings(final WorldSettings settings) { + tileSize = settings.tileSize; + tileHeight = settings.tileHeight; + gridSize = settings.gridSize; + + terrainTexture = settings.terrainTexture; + terrainTextureTileSize = settings.terrainTextureTileSize; + waterTexture = settings.waterTexture; + + useVBO = settings.useVBO; + doDefaultTint = settings.doDefaultTint; + + serverConnection = settings.serverConnection; + } + + /** + * Get chunk size in blocks. + * + * @return int + */ + public int getTileSize() { + return tileSize; + } + + /** + * Set size of chunks in blocks. + * + * @param tileSize + * int + */ + public void setTileSize(final int tileSize) { + this.tileSize = MathUtils.nearestPowerOfTwo(tileSize); + } + + /** + * Get height of chunks in blocks. + * + * @return int + */ + public int getTileHeight() { + return tileHeight; + } + + /** + * Set height of chunks in blocks. + * + * @param tileHeight + * int + */ + public void setTileHeight(final int tileHeight) { + this.tileHeight = tileHeight; + } + + /** + * Get length of world side, in chunks + * + * @return int + */ + public int getGridSize() { + return gridSize; + } + + /** + * Set length of world side, in chunks + * + * @param gridSize + * int + */ + public void setGridSize(final int gridSize) { + this.gridSize = gridSize + gridSize % 2; + } + + /** + * Get texture atlas used for terrain. + * + * @return ResourceSource + */ + public ResourceSource getTerrainTexture() { + return terrainTexture; + } + + /** + * Set texture atlas used for terrain. + * + * @param terrainTexture + * ResourceSource + */ + public void setTerrainTexture(final ResourceSource terrainTexture) { + this.terrainTexture = terrainTexture; + } + + /** + * Get size in pixels of each sub-texture tile in the texture atlas. + * + * @return int + */ + public int getTerrainTextureTileSize() { + return terrainTextureTileSize; + } + + /** + * Set size in pixels of each sub-texture tile in the texture atlas. (typically 16x16, 32x32 etc) + * + * @param terrainTextureTileSize + * int + */ + public void setTerrainTextureTileSize(final int terrainTextureTileSize) { + this.terrainTextureTileSize = terrainTextureTileSize; + } + + /** + * Get texture used for water. + * + * @return ResourceSource + */ + public ResourceSource getWaterTexture() { + return waterTexture; + } + + /** + * Set texture used for water. (current not used) + * + * @param waterTexture + * ResourceSource + */ + public void setWaterTexture(final ResourceSource waterTexture) { + this.waterTexture = waterTexture; + } + + /** + * Get server connection. + * + * @return IServerConnection + */ + public IServerConnection getServerConnection() { + return serverConnection; + } + + /** + * Set server connection. + * + * @param serverConnection + * IServerConnection + */ + public void setServerConnection(final IServerConnection serverConnection) { + this.serverConnection = serverConnection; + } + + /** + * Get if the world should use VBO (vertex buffer objects) for rendering. + * + * @return boolean + */ + public boolean isUseVBO() { + return useVBO; + } + + /** + * Set if the world should use VBO (vertex buffer objects) for rendering. Default is true. + * + * @param useVBO + * boolean + */ + public void setUseVBO(final boolean useVBO) { + this.useVBO = useVBO; + } + + /** + * Get if default minecraft texture tinting should be done. This will be turned into something completely + * configurable later on. + * + * @return boolean + */ + public boolean isDoDefaultTint() { + return doDefaultTint; + } + + /** + * Set if default minecraft texture tinting should be done. This will be turned into something completely + * configurable later on. + * + * @param doDefaultTint + */ + public void setDoDefaultTint(final boolean doDefaultTint) { + this.doDefaultTint = doDefaultTint; + } +} diff --git a/ardor3d-craft/src/main/java/com/ardorcraft/world/utils/ChunkDistanceComparator.java b/ardor3d-craft/src/main/java/com/ardorcraft/world/utils/ChunkDistanceComparator.java new file mode 100644 index 0000000..9ea4de2 --- /dev/null +++ b/ardor3d-craft/src/main/java/com/ardorcraft/world/utils/ChunkDistanceComparator.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2011-2012 Ardor Labs AB. + * + * This file is part of the ArdorCraft API, developed by Rikard Herlitz. + */ + +package com.ardorcraft.world.utils; + +import java.util.Comparator; + +import com.ardorcraft.data.Pos; + +public class ChunkDistanceComparator implements Comparator<Pos> { + private final int X; + private final int Z; + + public ChunkDistanceComparator(final int X, final int Z) { + this.X = X; + this.Z = Z; + } + + @Override + public int compare(final Pos o1, final Pos o2) { + int xDist = X - o1.x; + int zDist = Z - o1.z; + final int l1 = xDist * xDist + zDist * zDist; + xDist = X - o2.x; + zDist = Z - o2.z; + final int l2 = xDist * xDist + zDist * zDist; + return l1 - l2; + } +} diff --git a/ardor3d-craft/world.acr b/ardor3d-craft/world.acr Binary files differnew file mode 100644 index 0000000..ebac185 --- /dev/null +++ b/ardor3d-craft/world.acr diff --git a/ardor3d-examples/build.gradle b/ardor3d-examples/build.gradle index f07954b..7a6306c 100644 --- a/ardor3d-examples/build.gradle +++ b/ardor3d-examples/build.gradle @@ -12,4 +12,5 @@ dependencies { compile project(':ardor3d-collada') compile project(':ardor3d-terrain') compile project(':ardor3d-ui') + compile project(':ardor3d-craft') } diff --git a/ardor3d-examples/pom.xml b/ardor3d-examples/pom.xml index 62b3bf0..c9c25f1 100644 --- a/ardor3d-examples/pom.xml +++ b/ardor3d-examples/pom.xml @@ -162,6 +162,11 @@ <artifactId>ardor3d-ui</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>ardor3d-craft</artifactId> + <version>${project.version}</version> + </dependency> </dependencies> </project> @@ -63,6 +63,7 @@ <module>ardor3d-examples</module> <module>ardor3d-distribution</module> <module>ardor3d-performance</module> + <module>ardor3d-craft</module> </modules> <reporting> diff --git a/settings.gradle b/settings.gradle index 82cea1a..4b20feb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,6 +17,7 @@ include ':ardor3d-terrain' include ':ardor3d-examples' include ':ardor3d-performance' include ':distribution' +include ':ardor3d-craft' project(':ardor3d-savable').projectDir = "$rootDir/ardor3d-savable" as File project(':ardor3d-math').projectDir = "$rootDir/ardor3d-math" as File @@ -35,4 +36,5 @@ project(':ardor3d-ui').projectDir = "$rootDir/ardor3d-ui" as File project(':ardor3d-terrain').projectDir = "$rootDir/ardor3d-terrain" as File project(':ardor3d-examples').projectDir = "$rootDir/ardor3d-examples" as File project(':ardor3d-performance').projectDir = "$rootDir/ardor3d-performance" as File -project(':distribution').projectDir = "$rootDir/ardor3d-distribution" as File
\ No newline at end of file +project(':distribution').projectDir = "$rootDir/ardor3d-distribution" as File +project(':ardor3d-craft').projectDir = "$rootDir/ardor3d-craft" as File
\ No newline at end of file |