diff --git a/common.gradle b/common.gradle index d275c19f5..2cad48bf2 100644 --- a/common.gradle +++ b/common.gradle @@ -8,7 +8,7 @@ apply plugin: 'maven' group = 'org.jmonkeyengine' version = jmePomVersion -sourceCompatibility = '1.6' +sourceCompatibility = '1.7' [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' repositories { diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java index bddc5fab3..0a7fc3838 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java @@ -430,7 +430,7 @@ public class AndroidTouchInput implements TouchInput { return; } - logger.log(Level.INFO, "event: {0}", event); + //logger.log(Level.INFO, "event: {0}", event); inputEventQueue.add(event); if (event instanceof TouchEvent) { diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java index 1d76fc02f..12aff2f4f 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java @@ -215,8 +215,8 @@ public class Edge { /** * The method computes the crossing pint of this edge and another edge. If - * there is no crossing then null is returned. This method also allows to - * get the crossing point of the straight lines that contain these edges if + * there is no crossing then null is returned. Also null is returned if the edges are parallel. + * This method also allows to get the crossing point of the straight lines that contain these edges if * you set the 'extend' parameter to true. * * @param edge @@ -227,7 +227,7 @@ public class Edge { * @param extendSecondEdge * set to true to find a crossing point along the whole * straight that contains the given edge - * @return cross point on null if none exist + * @return cross point on null if none exist or the edges are parallel */ public Vector3f getCrossPoint(Edge edge, boolean extendThisEdge, boolean extendSecondEdge) { Vector3d P1 = new Vector3d(this.getFirstVertex()); @@ -235,6 +235,11 @@ public class Edge { Vector3d u = new Vector3d(this.getSecondVertex()).subtract(P1).normalizeLocal(); Vector3d v = new Vector3d(edge.getSecondVertex()).subtract(P2).normalizeLocal(); + if(Math.abs(u.dot(v)) >= 1 - FastMath.DBL_EPSILON) { + // the edges are parallel; do not care about the crossing point + return null; + } + double t1 = 0, t2 = 0; if(u.x == 0 && v.x == 0) { t2 = (u.z * (P2.y - P1.y) - u.y * (P2.z - P1.z)) / (u.y * v.z - u.z * v.y); @@ -262,11 +267,11 @@ public class Edge { // the lines cross, check if p1 and p2 are within the edges Vector3d p = p1.subtract(P1); double cos = p.dot(u) / p.length(); - if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= this.getLength()) { + if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - this.getLength() <= FastMath.FLT_EPSILON) { // p1 is inside the first edge, lets check the other edge now p = p2.subtract(P2); cos = p.dot(v) / p.length(); - if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() <= edge.getLength()) { + if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - edge.getLength() <= FastMath.FLT_EPSILON) { return p1.toVector3f(); } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java index a41d58ff0..97c730df1 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java @@ -146,7 +146,6 @@ public class Face implements Comparator { /** * @return current indexes of the face (if it is already triangulated then more than one index group will be in the result list) */ - @SuppressWarnings("unchecked") public List> getCurrentIndexes() { if (triangulatedFaces == null) { return Arrays.asList(indexes.getAll()); @@ -279,16 +278,6 @@ public class Face implements Comparator { // two special cases will improve the computations speed if(face.getIndexes().size() == 3) { triangulatedFaces.add(face.getIndexes().clone()); - } else if(face.getIndexes().size() == 4) { - // in case face has 4 verts we use the plain triangulation - indexes[0] = face.getIndex(0); - indexes[1] = face.getIndex(1); - indexes[2] = face.getIndex(2); - triangulatedFaces.add(new IndexesLoop(indexes)); - - indexes[1] = face.getIndex(2); - indexes[2] = face.getIndex(3); - triangulatedFaces.add(new IndexesLoop(indexes)); } else { int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1; while (face.vertexCount() > 0) { diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java index 5284b964a..49482c87f 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java @@ -286,30 +286,33 @@ public class MeshHelper extends AbstractBlenderHelper { List> result = new ArrayList>(); Structure parent = blenderContext.peekParent(); - Structure defbase = (Structure) parent.getFieldValue("defbase"); - List groupNames = new ArrayList(); - List defs = defbase.evaluateListBase(); - for (Structure def : defs) { - groupNames.add(def.getFieldValue("name").toString()); - } + if(parent != null) { + // the mesh might be saved without its parent (it is then unused) + Structure defbase = (Structure) parent.getFieldValue("defbase"); + List groupNames = new ArrayList(); + List defs = defbase.evaluateListBase(); + for (Structure def : defs) { + groupNames.add(def.getFieldValue("name").toString()); + } - Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices - if (pDvert.isNotNull()) {// assigning weights and bone indices - List dverts = pDvert.fetchData(); - for (Structure dvert : dverts) { - Map weightsForVertex = new HashMap(); - Pointer pDW = (Pointer) dvert.getFieldValue("dw"); - if (pDW.isNotNull()) { - List dw = pDW.fetchData(); - for (Structure deformWeight : dw) { - int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue(); - float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); - String groupName = groupNames.get(groupIndex); + Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices + if (pDvert.isNotNull()) {// assigning weights and bone indices + List dverts = pDvert.fetchData(); + for (Structure dvert : dverts) { + Map weightsForVertex = new HashMap(); + Pointer pDW = (Pointer) dvert.getFieldValue("dw"); + if (pDW.isNotNull()) { + List dw = pDW.fetchData(); + for (Structure deformWeight : dw) { + int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue(); + float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); + String groupName = groupNames.get(groupIndex); - weightsForVertex.put(groupName, weight); + weightsForVertex.put(groupName, weight); + } } + result.add(weightsForVertex); } - result.add(weightsForVertex); } } return result; diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java index 7c69fdf51..d4f3658c0 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java @@ -41,6 +41,8 @@ import com.jme3.math.Vector3f; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** @@ -49,7 +51,7 @@ import java.io.IOException; * * @author normenhansen */ -public abstract class AbstractPhysicsControl implements PhysicsControl { +public abstract class AbstractPhysicsControl implements PhysicsControl, JmeCloneable { private final Quaternion tmp_inverseWorldRotation = new Quaternion(); protected Spatial spatial; @@ -161,6 +163,12 @@ public abstract class AbstractPhysicsControl implements PhysicsControl { } + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + createSpatialData(this.spatial); + } + public void setSpatial(Spatial spatial) { if (this.spatial != null && this.spatial != spatial) { removeSpatialData(this.spatial); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java index c91c1be1d..30190f1f1 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java @@ -50,6 +50,8 @@ import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.List; import java.util.logging.Level; @@ -68,7 +70,7 @@ import java.util.logging.Logger; * * @author normenhansen */ -public class BetterCharacterControl extends AbstractPhysicsControl implements PhysicsTickListener { +public class BetterCharacterControl extends AbstractPhysicsControl implements PhysicsTickListener, JmeCloneable { protected static final Logger logger = Logger.getLogger(BetterCharacterControl.class.getName()); protected PhysicsRigidBody rigidBody; @@ -663,12 +665,21 @@ public class BetterCharacterControl extends AbstractPhysicsControl implements Ph rigidBody.setUserObject(null); } + @Override public Control cloneForSpatial(Spatial spatial) { BetterCharacterControl control = new BetterCharacterControl(radius, height, mass); control.setJumpForce(jumpForce); return control; } + @Override + public Object jmeClone() { + BetterCharacterControl control = new BetterCharacterControl(radius, height, mass); + control.setJumpForce(jumpForce); + control.spatial = this.spatial; + return control; + } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java index d521dbec7..7ef80778c 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java @@ -44,13 +44,15 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** * You might want to try BetterCharacterControl as well. * @author normenhansen */ -public class CharacterControl extends PhysicsCharacter implements PhysicsControl { +public class CharacterControl extends PhysicsCharacter implements PhysicsControl, JmeCloneable { protected Spatial spatial; protected boolean enabled = true; @@ -87,6 +89,7 @@ public class CharacterControl extends PhysicsCharacter implements PhysicsControl return spatial.getWorldTranslation(); } + @Override public Control cloneForSpatial(Spatial spatial) { CharacterControl control = new CharacterControl(collisionShape, stepHeight); control.setCcdMotionThreshold(getCcdMotionThreshold()); @@ -103,6 +106,29 @@ public class CharacterControl extends PhysicsCharacter implements PhysicsControl return control; } + @Override + public Object jmeClone() { + CharacterControl control = new CharacterControl(collisionShape, stepHeight); + control.setCcdMotionThreshold(getCcdMotionThreshold()); + control.setCcdSweptSphereRadius(getCcdSweptSphereRadius()); + control.setCollideWithGroups(getCollideWithGroups()); + control.setCollisionGroup(getCollisionGroup()); + control.setFallSpeed(getFallSpeed()); + control.setGravity(getGravity()); + control.setJumpSpeed(getJumpSpeed()); + control.setMaxSlope(getMaxSlope()); + control.setPhysicsLocation(getPhysicsLocation()); + control.setUpAxis(getUpAxis()); + control.setApplyPhysicsLocal(isApplyPhysicsLocal()); + control.spatial = this.spatial; + return control; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + } + public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java index 9ed150556..70c636507 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java @@ -44,6 +44,8 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** @@ -51,7 +53,7 @@ import java.io.IOException; * overlaps with other physics objects (e.g. aggro radius). * @author normenhansen */ -public class GhostControl extends PhysicsGhostObject implements PhysicsControl { +public class GhostControl extends PhysicsGhostObject implements PhysicsControl, JmeCloneable { protected Spatial spatial; protected boolean enabled = true; @@ -93,6 +95,7 @@ public class GhostControl extends PhysicsGhostObject implements PhysicsControl { return spatial.getWorldRotation(); } + @Override public Control cloneForSpatial(Spatial spatial) { GhostControl control = new GhostControl(collisionShape); control.setCcdMotionThreshold(getCcdMotionThreshold()); @@ -105,6 +108,25 @@ public class GhostControl extends PhysicsGhostObject implements PhysicsControl { return control; } + @Override + public Object jmeClone() { + GhostControl control = new GhostControl(collisionShape); + control.setCcdMotionThreshold(getCcdMotionThreshold()); + control.setCcdSweptSphereRadius(getCcdSweptSphereRadius()); + control.setCollideWithGroups(getCollideWithGroups()); + control.setCollisionGroup(getCollisionGroup()); + control.setPhysicsLocation(getPhysicsLocation()); + control.setPhysicsRotation(getPhysicsRotationMatrix()); + control.setApplyPhysicsLocal(isApplyPhysicsLocal()); + control.spatial = this.spatial; + return control; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + } + public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java index d81d6a267..6c25b6662 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java @@ -61,6 +61,8 @@ import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.*; import java.util.logging.Level; @@ -92,7 +94,7 @@ import java.util.logging.Logger; * * @author Normen Hansen and Rémy Bouquet (Nehon) */ -public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener { +public class KinematicRagdollControl extends AbstractPhysicsControl implements PhysicsCollisionListener, JmeCloneable { protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName()); protected List listeners; @@ -910,6 +912,7 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P public void render(RenderManager rm, ViewPort vp) { } + @Override public Control cloneForSpatial(Spatial spatial) { KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold); control.setMode(mode); @@ -919,6 +922,17 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P return control; } + @Override + public Object jmeClone() { + KinematicRagdollControl control = new KinematicRagdollControl(preset, weightThreshold); + control.setMode(mode); + control.setRootMass(rootMass); + control.setWeightThreshold(weightThreshold); + control.setApplyPhysicsLocal(applyLocal); + control.spatial = this.spatial; + return control; + } + public Vector3f setIKTarget(Bone bone, Vector3f worldPos, int chainLength) { Vector3f target = worldPos.subtract(targetModel.getWorldTranslation()); ikTargets.put(bone.getName(), target); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java index baad952a0..25a23cc7c 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java @@ -51,13 +51,15 @@ import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Sphere; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** * * @author normenhansen */ -public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl { +public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl, JmeCloneable { protected Spatial spatial; protected boolean enabled = true; @@ -89,6 +91,7 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl super(shape, mass); } + @Override public Control cloneForSpatial(Spatial spatial) { RigidBodyControl control = new RigidBodyControl(collisionShape, mass); control.setAngularFactor(getAngularFactor()); @@ -115,6 +118,39 @@ public class RigidBodyControl extends PhysicsRigidBody implements PhysicsControl return control; } + @Override + public Object jmeClone() { + RigidBodyControl control = new RigidBodyControl(collisionShape, mass); + control.setAngularFactor(getAngularFactor()); + control.setAngularSleepingThreshold(getAngularSleepingThreshold()); + control.setCcdMotionThreshold(getCcdMotionThreshold()); + control.setCcdSweptSphereRadius(getCcdSweptSphereRadius()); + control.setCollideWithGroups(getCollideWithGroups()); + control.setCollisionGroup(getCollisionGroup()); + control.setDamping(getLinearDamping(), getAngularDamping()); + control.setFriction(getFriction()); + control.setGravity(getGravity()); + control.setKinematic(isKinematic()); + control.setKinematicSpatial(isKinematicSpatial()); + control.setLinearSleepingThreshold(getLinearSleepingThreshold()); + control.setPhysicsLocation(getPhysicsLocation(null)); + control.setPhysicsRotation(getPhysicsRotationMatrix(null)); + control.setRestitution(getRestitution()); + + if (mass > 0) { + control.setAngularVelocity(getAngularVelocity()); + control.setLinearVelocity(getLinearVelocity()); + } + control.setApplyPhysicsLocal(isApplyPhysicsLocal()); + control.spatial = this.spatial; + return control; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + } + public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java b/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java index 0dc033331..7ad36b3ae 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java +++ b/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java @@ -46,6 +46,8 @@ import com.jme3.renderer.ViewPort; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.Iterator; @@ -53,7 +55,7 @@ import java.util.Iterator; * * @author normenhansen */ -public class VehicleControl extends PhysicsVehicle implements PhysicsControl { +public class VehicleControl extends PhysicsVehicle implements PhysicsControl, JmeCloneable { protected Spatial spatial; protected boolean enabled = true; @@ -106,6 +108,7 @@ public class VehicleControl extends PhysicsVehicle implements PhysicsControl { return spatial.getWorldRotation(); } + @Override public Control cloneForSpatial(Spatial spatial) { VehicleControl control = new VehicleControl(collisionShape, mass); control.setAngularFactor(getAngularFactor()); @@ -155,6 +158,63 @@ public class VehicleControl extends PhysicsVehicle implements PhysicsControl { return control; } + @Override + public Object jmeClone() { + VehicleControl control = new VehicleControl(collisionShape, mass); + control.setAngularFactor(getAngularFactor()); + control.setAngularSleepingThreshold(getAngularSleepingThreshold()); + control.setAngularVelocity(getAngularVelocity()); + control.setCcdMotionThreshold(getCcdMotionThreshold()); + control.setCcdSweptSphereRadius(getCcdSweptSphereRadius()); + control.setCollideWithGroups(getCollideWithGroups()); + control.setCollisionGroup(getCollisionGroup()); + control.setDamping(getLinearDamping(), getAngularDamping()); + control.setFriction(getFriction()); + control.setGravity(getGravity()); + control.setKinematic(isKinematic()); + control.setLinearSleepingThreshold(getLinearSleepingThreshold()); + control.setLinearVelocity(getLinearVelocity()); + control.setPhysicsLocation(getPhysicsLocation()); + control.setPhysicsRotation(getPhysicsRotationMatrix()); + control.setRestitution(getRestitution()); + + control.setFrictionSlip(getFrictionSlip()); + control.setMaxSuspensionTravelCm(getMaxSuspensionTravelCm()); + control.setSuspensionStiffness(getSuspensionStiffness()); + control.setSuspensionCompression(tuning.suspensionCompression); + control.setSuspensionDamping(tuning.suspensionDamping); + control.setMaxSuspensionForce(getMaxSuspensionForce()); + + for (Iterator it = wheels.iterator(); it.hasNext();) { + VehicleWheel wheel = it.next(); + VehicleWheel newWheel = control.addWheel(wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), wheel.isFrontWheel()); + newWheel.setFrictionSlip(wheel.getFrictionSlip()); + newWheel.setMaxSuspensionTravelCm(wheel.getMaxSuspensionTravelCm()); + newWheel.setSuspensionStiffness(wheel.getSuspensionStiffness()); + newWheel.setWheelsDampingCompression(wheel.getWheelsDampingCompression()); + newWheel.setWheelsDampingRelaxation(wheel.getWheelsDampingRelaxation()); + newWheel.setMaxSuspensionForce(wheel.getMaxSuspensionForce()); + + // Copy the wheel spatial reference directly for now. They'll + // get fixed up in the cloneFields() method + newWheel.setWheelSpatial(wheel.getWheelSpatial()); + } + control.setApplyPhysicsLocal(isApplyPhysicsLocal()); + + control.spatial = spatial; + return control; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + + for( VehicleWheel wheel : wheels ) { + Spatial spatial = cloner.clone(wheel.getWheelSpatial()); + wheel.setWheelSpatial(spatial); + } + } + public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java index bed5caaee..3dd317624 100644 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java +++ b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java @@ -70,6 +70,10 @@ public class ConeCollisionShape extends CollisionShape { public float getRadius() { return radius; } + + public float getHeight() { + return height; + } public void write(JmeExporter ex) throws IOException { super.write(ex); diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimControl.java b/jme3-core/src/main/java/com/jme3/animation/AnimControl.java index 13328cddf..36dbb6be5 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimControl.java @@ -38,11 +38,14 @@ import com.jme3.scene.Mesh; import com.jme3.scene.Spatial; import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import com.jme3.util.TempVars; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; /** @@ -65,7 +68,7 @@ import java.util.Map.Entry; * * @author Kirill Vainer */ -public final class AnimControl extends AbstractControl implements Cloneable { +public final class AnimControl extends AbstractControl implements Cloneable, JmeCloneable { /** * Skeleton object must contain corresponding data for the targets' weight buffers. @@ -108,6 +111,7 @@ public final class AnimControl extends AbstractControl implements Cloneable { /** * Internal use only. */ + @Override public Control cloneForSpatial(Spatial spatial) { try { AnimControl clone = (AnimControl) super.clone(); @@ -130,6 +134,32 @@ public final class AnimControl extends AbstractControl implements Cloneable { } } + @Override + public Object jmeClone() { + AnimControl clone = (AnimControl) super.jmeClone(); + clone.channels = new ArrayList(); + clone.listeners = new ArrayList(); + + return clone; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + super.cloneFields(cloner, original); + + this.skeleton = cloner.clone(skeleton); + + // Note cloneForSpatial() never actually cloned the animation map... just its reference + HashMap newMap = new HashMap<>(); + + // animationMap is cloned, but only ClonableTracks will be cloned as they need a reference to a cloned spatial + for( Map.Entry e : animationMap.entrySet() ) { + newMap.put(e.getKey(), cloner.clone(e.getValue())); + } + + this.animationMap = newMap; + } + /** * @param animations Set the animations that this AnimControl * will be capable of playing. The animations should be compatible diff --git a/jme3-core/src/main/java/com/jme3/animation/Animation.java b/jme3-core/src/main/java/com/jme3/animation/Animation.java index 5ae34628e..9fe47db9f 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Animation.java +++ b/jme3-core/src/main/java/com/jme3/animation/Animation.java @@ -35,6 +35,8 @@ import com.jme3.export.*; import com.jme3.scene.Spatial; import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** @@ -42,7 +44,7 @@ import java.io.IOException; * * @author Kirill Vainer, Marcin Roguski (Kaelthas) */ -public class Animation implements Savable, Cloneable { +public class Animation implements Savable, Cloneable, JmeCloneable { /** * The name of the animation. @@ -190,6 +192,33 @@ public class Animation implements Savable, Cloneable { } } + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException("Error cloning", e); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + + // There is some logic here that I'm copying but I'm not sure if + // it's a mistake or not. If a track is not a CloneableTrack then it + // isn't cloned at all... even though they all implement clone() methods. -pspeed + SafeArrayList newTracks = new SafeArrayList<>(Track.class); + for( Track track : tracks ) { + if( track instanceof ClonableTrack ) { + newTracks.add(cloner.clone(track)); + } else { + // this is the part that seems fishy + newTracks.add(track); + } + } + this.tracks = newTracks; + } + @Override public String toString() { return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']'; diff --git a/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java b/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java index 7bb5ca0ce..fc67686d2 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java @@ -39,6 +39,8 @@ import com.jme3.export.OutputCapsule; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -174,6 +176,7 @@ public class AudioTrack implements ClonableTrack { * @param spatial the Spatial holding the AnimControl * @return the cloned Track with proper reference */ + @Override public Track cloneForSpatial(Spatial spatial) { AudioTrack audioTrack = new AudioTrack(); audioTrack.length = this.length; @@ -192,7 +195,27 @@ public class AudioTrack implements ClonableTrack { return audioTrack; } - /** + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException("Error cloning", e); + } + } + + + @Override + public void cloneFields( Cloner cloner, Object original ) { + // Duplicating the old cloned state from cloneForSpatial() + this.initialized = false; + this.started = false; + this.played = false; + this.audio = cloner.clone(audio); + } + + + /** * recursive function responsible for finding the newly cloned AudioNode * * @param spat diff --git a/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java b/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java index ae7ae6703..bfdd87abd 100644 --- a/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java @@ -32,6 +32,7 @@ package com.jme3.animation; import com.jme3.scene.Spatial; +import com.jme3.util.clone.JmeCloneable; /** * An interface that allow to clone a Track for a given Spatial. @@ -43,7 +44,7 @@ import com.jme3.scene.Spatial; * * @author Nehon */ -public interface ClonableTrack extends Track { +public interface ClonableTrack extends Track, JmeCloneable { /** * Allows to clone the track for a given Spatial. diff --git a/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java b/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java index 85674e223..82af6e135 100644 --- a/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java @@ -44,6 +44,8 @@ import com.jme3.scene.Spatial.CullHint; import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.Control; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -116,6 +118,22 @@ public class EffectTrack implements ClonableTrack { } } + @Override + public Object jmeClone() { + KillParticleControl c = new KillParticleControl(); + //this control should be removed as it shouldn't have been persisted in the first place + //In the quest to find the less hackish solution to achieve this, + //making it remove itself from the spatial in the first update loop when loaded was the less bad. + c.remove = true; + c.spatial = spatial; + return c; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + } + @Override protected void controlRender(RenderManager rm, ViewPort vp) { } @@ -263,6 +281,7 @@ public class EffectTrack implements ClonableTrack { * @param spatial the Spatial holding the AnimControl * @return the cloned Track with proper reference */ + @Override public Track cloneForSpatial(Spatial spatial) { EffectTrack effectTrack = new EffectTrack(); effectTrack.particlesPerSeconds = this.particlesPerSeconds; @@ -283,6 +302,21 @@ public class EffectTrack implements ClonableTrack { return effectTrack; } + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException("Error cloning", e); + } + } + + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.emitter = cloner.clone(emitter); + } + /** * recursive function responsible for finding the newly cloned Emitter * diff --git a/jme3-core/src/main/java/com/jme3/animation/Skeleton.java b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java index b0d3419d8..8ae3ca507 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Skeleton.java +++ b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java @@ -34,6 +34,8 @@ package com.jme3.animation; import com.jme3.export.*; import com.jme3.math.Matrix4f; import com.jme3.util.TempVars; +import com.jme3.util.clone.JmeCloneable; +import com.jme3.util.clone.Cloner; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -45,7 +47,7 @@ import java.util.List; * * @author Kirill Vainer */ -public final class Skeleton implements Savable { +public final class Skeleton implements Savable, JmeCloneable { private Bone[] rootBones; private Bone[] boneList; @@ -118,6 +120,15 @@ public final class Skeleton implements Savable { public Skeleton() { } + @Override + public Object jmeClone() { + return new Skeleton(this); + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + } + private void createSkinningMatrices() { skinningMatrixes = new Matrix4f[boneList.length]; for (int i = 0; i < skinningMatrixes.length; i++) { diff --git a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java index b1f3d02df..2e0b17c3b 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java @@ -46,6 +46,8 @@ import com.jme3.scene.control.Control; import com.jme3.shader.VarType; import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.nio.Buffer; import java.nio.ByteBuffer; @@ -63,7 +65,7 @@ import java.util.logging.Logger; * * @author Rémy Bouquet Based on AnimControl by Kirill Vainer */ -public class SkeletonControl extends AbstractControl implements Cloneable { +public class SkeletonControl extends AbstractControl implements Cloneable, JmeCloneable { /** * The skeleton of the model. @@ -345,6 +347,7 @@ public class SkeletonControl extends AbstractControl implements Cloneable { } } + @Override public Control cloneForSpatial(Spatial spatial) { Node clonedNode = (Node) spatial; SkeletonControl clone = new SkeletonControl(); @@ -385,6 +388,29 @@ public class SkeletonControl extends AbstractControl implements Cloneable { return clone; } + @Override + public Object jmeClone() { + return super.jmeClone(); + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + super.cloneFields(cloner, original); + + this.skeleton = cloner.clone(skeleton); + + // If the targets were cloned then this will clone them. If the targets + // were shared then this will share them. + this.targets = cloner.clone(targets); + + // Not automatic set cloning yet + Set newMaterials = new HashSet(); + for( Material m : this.materials ) { + newMaterials.add(cloner.clone(m)); + } + this.materials = newMaterials; + } + /** * * @param boneName the name of the bone diff --git a/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java b/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java index b6bff101a..7ee927cd3 100644 --- a/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java +++ b/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java @@ -36,6 +36,8 @@ import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.export.Savable; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.ArrayList; @@ -48,7 +50,7 @@ import java.util.ArrayList; * * @author Nehon */ -public class TrackInfo implements Savable { +public class TrackInfo implements Savable, JmeCloneable { ArrayList tracks = new ArrayList(); @@ -72,4 +74,18 @@ public class TrackInfo implements Savable { public void addTrack(Track track) { tracks.add(track); } + + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException("Error cloning", e); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.tracks = cloner.clone(tracks); + } } diff --git a/jme3-core/src/main/java/com/jme3/app/StatsView.java b/jme3-core/src/main/java/com/jme3/app/StatsView.java index a0446e85e..8b88833c2 100644 --- a/jme3-core/src/main/java/com/jme3/app/StatsView.java +++ b/jme3-core/src/main/java/com/jme3/app/StatsView.java @@ -41,6 +41,8 @@ import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; /** * The StatsView provides a heads-up display (HUD) of various @@ -58,7 +60,7 @@ import com.jme3.scene.control.Control; * rootNode.attachChild(statsView);
* */ -public class StatsView extends Node implements Control { +public class StatsView extends Node implements Control, JmeCloneable { private BitmapText statText; private Statistics statistics; @@ -67,7 +69,7 @@ public class StatsView extends Node implements Control { private int[] statData; private boolean enabled = true; - + private final StringBuilder stringBuilder = new StringBuilder(); public StatsView(String name, AssetManager manager, Statistics stats){ @@ -93,32 +95,43 @@ public class StatsView extends Node implements Control { public float getHeight() { return statText.getLineHeight() * statLabels.length; } - + public void update(float tpf) { - - if (!isEnabled()) + + if (!isEnabled()) return; - + statistics.getData(statData); stringBuilder.setLength(0); - - // Need to walk through it backwards, as the first label + + // Need to walk through it backwards, as the first label // should appear at the bottom, not the top. for (int i = statLabels.length - 1; i >= 0; i--) { stringBuilder.append(statLabels[i]).append(" = ").append(statData[i]).append('\n'); } statText.setText(stringBuilder); - + // Moved to ResetStatsState to make sure it is // done even if there is no StatsView or the StatsView // is disable. //statistics.clearFrame(); } + @Override public Control cloneForSpatial(Spatial spatial) { return (Control) spatial; } + @Override + public StatsView jmeClone() { + throw new UnsupportedOperationException("Not yet implemented."); + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + throw new UnsupportedOperationException("Not yet implemented."); + } + public void setSpatial(Spatial spatial) { } diff --git a/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java b/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java index f386af5db..c6ba5b124 100644 --- a/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java @@ -249,21 +249,23 @@ public class ScreenshotAppState extends AbstractAppState implements ActionListen } logger.log(Level.FINE, "Saving ScreenShot to: {0}", file.getAbsolutePath()); - OutputStream outStream = null; try { - outStream = new FileOutputStream(file); - JmeSystem.writeImageFile(outStream, "png", outBuf, width, height); + writeImageFile(file); } catch (IOException ex) { logger.log(Level.SEVERE, "Error while saving screenshot", ex); - } finally { - if (outStream != null){ - try { - outStream.close(); - } catch (IOException ex) { - logger.log(Level.SEVERE, "Error while saving screenshot", ex); - } - } - } + } } } + + /** + * Called by postFrame() once the screen has been captured to outBuf. + */ + protected void writeImageFile( File file ) throws IOException { + OutputStream outStream = new FileOutputStream(file); + try { + JmeSystem.writeImageFile(outStream, "png", outBuf, width, height); + } finally { + outStream.close(); + } + } } diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java index 2349461b5..47788275b 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java @@ -47,6 +47,8 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** @@ -56,7 +58,7 @@ import java.io.IOException; * * @author Nehon */ -public class MotionEvent extends AbstractCinematicEvent implements Control { +public class MotionEvent extends AbstractCinematicEvent implements Control, JmeCloneable { protected Spatial spatial; protected int currentWayPoint; @@ -118,7 +120,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control { */ public MotionEvent(Spatial spatial, MotionPath path) { super(); - this.spatial = spatial; spatial.addControl(this); this.path = path; } @@ -130,7 +131,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control { */ public MotionEvent(Spatial spatial, MotionPath path, float initialDuration) { super(initialDuration); - this.spatial = spatial; spatial.addControl(this); this.path = path; } @@ -142,7 +142,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control { */ public MotionEvent(Spatial spatial, MotionPath path, LoopMode loopMode) { super(); - this.spatial = spatial; spatial.addControl(this); this.path = path; this.loopMode = loopMode; @@ -155,7 +154,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control { */ public MotionEvent(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) { super(initialDuration); - this.spatial = spatial; spatial.addControl(this); this.path = path; this.loopMode = loopMode; @@ -274,8 +272,10 @@ public class MotionEvent extends AbstractCinematicEvent implements Control { * @param spatial * @return */ + @Override public Control cloneForSpatial(Spatial spatial) { - MotionEvent control = new MotionEvent(spatial, path); + MotionEvent control = new MotionEvent(); + control.setPath(path); control.playState = playState; control.currentWayPoint = currentWayPoint; control.currentValue = currentValue; @@ -291,6 +291,31 @@ public class MotionEvent extends AbstractCinematicEvent implements Control { return control; } + @Override + public Object jmeClone() { + MotionEvent control = new MotionEvent(); + control.path = path; + control.playState = playState; + control.currentWayPoint = currentWayPoint; + control.currentValue = currentValue; + control.direction = direction.clone(); + control.lookAt = lookAt.clone(); + control.upVector = upVector.clone(); + control.rotation = rotation.clone(); + control.initialDuration = initialDuration; + control.speed = speed; + control.loopMode = loopMode; + control.directionType = directionType; + control.spatial = spatial; + + return control; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + } + @Override public void onPlay() { traveledDistance = 0; diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java index ca3467781..d53273c4e 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleEmitter.java @@ -54,6 +54,8 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** @@ -108,7 +110,7 @@ public class ParticleEmitter extends Geometry { private transient Vector3f temp = new Vector3f(); private transient Vector3f lastPos; - public static class ParticleEmitterControl implements Control { + public static class ParticleEmitterControl implements Control, JmeCloneable { ParticleEmitter parentEmitter; @@ -119,11 +121,26 @@ public class ParticleEmitter extends Geometry { this.parentEmitter = parentEmitter; } + @Override public Control cloneForSpatial(Spatial spatial) { return this; // WARNING: Sets wrong control on spatial. Will be // fixed automatically by ParticleEmitter.clone() method. } + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException("Error cloning", e); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.parentEmitter = cloner.clone(parentEmitter); + } + public void setSpatial(Spatial spatial) { } diff --git a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java index d636858c7..de2c6e8ae 100644 --- a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java +++ b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java @@ -43,13 +43,15 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** * A camera that follows a spatial and can turn around it by dragging the mouse * @author nehon */ -public class ChaseCamera implements ActionListener, AnalogListener, Control { +public class ChaseCamera implements ActionListener, AnalogListener, Control, JmeCloneable { protected Spatial target = null; protected float minVerticalRotation = 0.00f; @@ -567,6 +569,7 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control { * @param spatial * @return */ + @Override public Control cloneForSpatial(Spatial spatial) { ChaseCamera cc = new ChaseCamera(cam, spatial, inputManager); cc.setMaxDistance(getMaxDistance()); @@ -574,6 +577,23 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control { return cc; } + @Override + public Object jmeClone() { + ChaseCamera cc = new ChaseCamera(cam, inputManager); + cc.target = target; + cc.setMaxDistance(getMaxDistance()); + cc.setMinDistance(getMinDistance()); + return cc; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.target = cloner.clone(target); + computePosition(); + prevPos = new Vector3f(target.getWorldTranslation()); + cam.setLocation(pos); + } + /** * Sets the spacial for the camera control, should only be used internally * @param spatial diff --git a/jme3-core/src/main/java/com/jme3/light/LightList.java b/jme3-core/src/main/java/com/jme3/light/LightList.java index fc08df2c2..dfeb65405 100644 --- a/jme3-core/src/main/java/com/jme3/light/LightList.java +++ b/jme3-core/src/main/java/com/jme3/light/LightList.java @@ -33,6 +33,8 @@ package com.jme3.light; import com.jme3.export.*; import com.jme3.scene.Spatial; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import com.jme3.util.SortUtil; import java.io.IOException; import java.util.*; @@ -40,10 +42,10 @@ import java.util.*; /** * LightList is used internally by {@link Spatial}s to manage * lights that are attached to them. - * + * * @author Kirill Vainer */ -public final class LightList implements Iterable, Savable, Cloneable { +public final class LightList implements Iterable, Savable, Cloneable, JmeCloneable { private Light[] list, tlist; private float[] distToOwner; @@ -74,7 +76,7 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Creates a LightList for the given {@link Spatial}. - * + * * @param owner The spatial owner */ public LightList(Spatial owner) { @@ -87,7 +89,7 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Set the owner of the LightList. Only used for cloning. - * @param owner + * @param owner */ public void setOwner(Spatial owner){ this.owner = owner; @@ -118,7 +120,7 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Remove the light at the given index. - * + * * @param index */ public void remove(int index){ @@ -139,7 +141,7 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Removes the given light from the LightList. - * + * * @param l the light to remove */ public void remove(Light l){ @@ -187,12 +189,12 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Sorts the elements in the list according to their Comparator. - * There are two reasons why lights should be resorted. - * First, if the lights have moved, that means their distance to - * the spatial changed. - * Second, if the spatial itself moved, it means the distance from it to + * There are two reasons why lights should be resorted. + * First, if the lights have moved, that means their distance to + * the spatial changed. + * Second, if the spatial itself moved, it means the distance from it to * the individual lights might have changed. - * + * * * @param transformChanged Whether the spatial's transform has changed */ @@ -252,7 +254,7 @@ public final class LightList implements Iterable, Savable, Cloneable { list[p] = parent.list[i]; distToOwner[p] = Float.NEGATIVE_INFINITY; } - + listSize = local.listSize + parent.listSize; }else{ listSize = local.listSize; @@ -261,7 +263,7 @@ public final class LightList implements Iterable, Savable, Cloneable { /** * Returns an iterator that can be used to iterate over this LightList. - * + * * @return an iterator that can be used to iterate over this LightList. */ public Iterator iterator() { @@ -276,10 +278,10 @@ public final class LightList implements Iterable, Savable, Cloneable { public Light next() { if (!hasNext()) throw new NoSuchElementException(); - + return list[index++]; } - + public void remove() { LightList.this.remove(--index); } @@ -290,7 +292,7 @@ public final class LightList implements Iterable, Savable, Cloneable { public LightList clone(){ try{ LightList clone = (LightList) super.clone(); - + clone.owner = null; clone.list = list.clone(); clone.distToOwner = distToOwner.clone(); @@ -302,6 +304,24 @@ public final class LightList implements Iterable, Savable, Cloneable { } } + @Override + public LightList jmeClone() { + try{ + LightList clone = (LightList)super.clone(); + clone.tlist = null; // list used for sorting only + return clone; + }catch (CloneNotSupportedException ex){ + throw new AssertionError(); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.owner = cloner.clone(owner); + this.list = cloner.clone(list); + this.distToOwner = cloner.clone(distToOwner); + } + public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); // oc.write(owner, "owner", null); @@ -319,7 +339,7 @@ public final class LightList implements Iterable, Savable, Cloneable { List lights = ic.readSavableArrayList("lights", null); listSize = lights.size(); - + // NOTE: make sure the array has a length of at least 1 int arraySize = Math.max(DEFAULT_SIZE, listSize); list = new Light[arraySize]; @@ -328,7 +348,7 @@ public final class LightList implements Iterable, Savable, Cloneable { for (int i = 0; i < listSize; i++){ list[i] = lights.get(i); } - + Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY); } diff --git a/jme3-core/src/main/java/com/jme3/material/MatParam.java b/jme3-core/src/main/java/com/jme3/material/MatParam.java index b94e40fc9..677c17d55 100644 --- a/jme3-core/src/main/java/com/jme3/material/MatParam.java +++ b/jme3-core/src/main/java/com/jme3/material/MatParam.java @@ -248,16 +248,45 @@ When arrays can be inserted in J3M files if (texKey.isFlipY()) { ret += "Flip "; } - if (texVal.getWrap(Texture.WrapAxis.S) == WrapMode.Repeat) { - ret += "Repeat "; + + //Wrap mode + ret += getWrapMode(texVal, Texture.WrapAxis.S); + ret += getWrapMode(texVal, Texture.WrapAxis.T); + ret += getWrapMode(texVal, Texture.WrapAxis.R); + + //Min and Mag filter + Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps; + if(texVal.getImage().hasMipmaps() || texKey.isGenerateMips()){ + def = Texture.MinFilter.Trilinear; + } + if(texVal.getMinFilter() != def){ + ret += "Min" + texVal.getMinFilter().name()+ " "; + } + + if(texVal.getMagFilter() != Texture.MagFilter.Bilinear){ + ret += "Mag" + texVal.getMagFilter().name()+ " "; } - return ret + texKey.getName(); + return ret + "\"" + texKey.getName() + "\""; default: return null; // parameter type not supported in J3M } } + private String getWrapMode(Texture texVal, Texture.WrapAxis axis) { + WrapMode mode = WrapMode.EdgeClamp; + try{ + mode = texVal.getWrap(axis); + }catch (IllegalArgumentException e){ + //this axis doesn't exist on the texture + return ""; + } + if(mode != WrapMode.EdgeClamp){ + return"Wrap"+ mode.name() + "_" + axis.name() + " "; + } + return ""; + } + @Override public MatParam clone() { try { diff --git a/jme3-core/src/main/java/com/jme3/material/RenderState.java b/jme3-core/src/main/java/com/jme3/material/RenderState.java index 9359ce7f4..94cae3f50 100644 --- a/jme3-core/src/main/java/com/jme3/material/RenderState.java +++ b/jme3-core/src/main/java/com/jme3/material/RenderState.java @@ -311,6 +311,8 @@ public class RenderState implements Cloneable, Savable { boolean applyPolyOffset = true; boolean stencilTest = false; boolean applyStencilTest = false; + float lineWidth = 1; + boolean applyLineWidth = false; TestFunction depthFunc = TestFunction.LessOrEqual; //by default depth func will be applied anyway if depth test is applied boolean applyDepthFunc = false; @@ -350,6 +352,9 @@ public class RenderState implements Cloneable, Savable { oc.write(backStencilDepthPassOperation, "backStencilDepthPassOperation", StencilOperation.Keep); oc.write(frontStencilFunction, "frontStencilFunction", TestFunction.Always); oc.write(backStencilFunction, "backStencilFunction", TestFunction.Always); + oc.write(depthFunc, "depthFunc", TestFunction.LessOrEqual); + oc.write(alphaFunc, "alphaFunc", TestFunction.Greater); + oc.write(lineWidth, "lineWidth", 1); // Only "additional render state" has them set to false by default oc.write(applyPointSprite, "applyPointSprite", true); @@ -364,8 +369,7 @@ public class RenderState implements Cloneable, Savable { oc.write(applyPolyOffset, "applyPolyOffset", true); oc.write(applyDepthFunc, "applyDepthFunc", true); oc.write(applyAlphaFunc, "applyAlphaFunc", false); - oc.write(depthFunc, "depthFunc", TestFunction.LessOrEqual); - oc.write(alphaFunc, "alphaFunc", TestFunction.Greater); + oc.write(applyLineWidth, "applyLineWidth", true); } @@ -394,6 +398,8 @@ public class RenderState implements Cloneable, Savable { backStencilFunction = ic.readEnum("backStencilFunction", TestFunction.class, TestFunction.Always); depthFunc = ic.readEnum("depthFunc", TestFunction.class, TestFunction.LessOrEqual); alphaFunc = ic.readEnum("alphaFunc", TestFunction.class, TestFunction.Greater); + lineWidth = ic.readFloat("lineWidth", 1); + applyPointSprite = ic.readBoolean("applyPointSprite", true); applyWireFrame = ic.readBoolean("applyWireFrame", true); @@ -407,6 +413,8 @@ public class RenderState implements Cloneable, Savable { applyPolyOffset = ic.readBoolean("applyPolyOffset", true); applyDepthFunc = ic.readBoolean("applyDepthFunc", true); applyAlphaFunc = ic.readBoolean("applyAlphaFunc", false); + applyLineWidth = ic.readBoolean("applyLineWidth", true); + } @@ -528,6 +536,10 @@ public class RenderState implements Cloneable, Savable { } } + if(lineWidth != rs.lineWidth){ + return false; + } + return true; } @@ -803,8 +815,17 @@ public class RenderState implements Cloneable, Savable { this.alphaFunc = alphaFunc; cachedHashCode = -1; } - - + + /** + * Sets the mesh line width. + * This is to use in conjunction with {@link #setWireframe(boolean)} or with a mesh in {@link Mesh.Mode#Lines} mode. + * @param lineWidth the line width. + */ + public void setLineWidth(float lineWidth) { + this.lineWidth = lineWidth; + this.applyLineWidth = true; + cachedHashCode = -1; + } /** * Check if stencil test is enabled. @@ -1118,8 +1139,16 @@ public class RenderState implements Cloneable, Savable { public TestFunction getAlphaFunc() { return alphaFunc; } - - + + /** + * returns the wireframe line width + * + * @return the line width + */ + public float getLineWidth() { + return lineWidth; + } + public boolean isApplyAlphaFallOff() { return applyAlphaFallOff; @@ -1168,8 +1197,10 @@ public class RenderState implements Cloneable, Savable { public boolean isApplyAlphaFunc() { return applyAlphaFunc; } - - + + public boolean isApplyLineWidth() { + return applyLineWidth; + } /** * @@ -1200,6 +1231,7 @@ public class RenderState implements Cloneable, Savable { hash = 79 * hash + (this.backStencilDepthPassOperation != null ? this.backStencilDepthPassOperation.hashCode() : 0); hash = 79 * hash + (this.frontStencilFunction != null ? this.frontStencilFunction.hashCode() : 0); hash = 79 * hash + (this.backStencilFunction != null ? this.backStencilFunction.hashCode() : 0); + hash = 79 * hash + Float.floatToIntBits(this.lineWidth); cachedHashCode = hash; } return cachedHashCode; @@ -1324,6 +1356,11 @@ public class RenderState implements Cloneable, Savable { state.frontStencilFunction = frontStencilFunction; state.backStencilFunction = backStencilFunction; } + if (additionalState.applyLineWidth) { + state.lineWidth = additionalState.lineWidth; + } else { + state.lineWidth = lineWidth; + } state.cachedHashCode = -1; return state; } @@ -1351,6 +1388,7 @@ public class RenderState implements Cloneable, Savable { backStencilFunction = state.backStencilFunction; depthFunc = state.depthFunc; alphaFunc = state.alphaFunc; + lineWidth = state.lineWidth; applyPointSprite = true; applyWireFrame = true; @@ -1364,6 +1402,7 @@ public class RenderState implements Cloneable, Savable { applyPolyOffset = true; applyDepthFunc = true; applyAlphaFunc = false; + applyLineWidth = true; } @Override @@ -1392,7 +1431,8 @@ public class RenderState implements Cloneable, Savable { + "\noffsetEnabled=" + offsetEnabled + "\napplyPolyOffset=" + applyPolyOffset + "\noffsetFactor=" + offsetFactor - + "\noffsetUnits=" + offsetUnits + + "\noffsetUnits=" + offsetUnits + + "\nlineWidth=" + lineWidth + "\n]"; } } diff --git a/jme3-core/src/main/java/com/jme3/math/Spline.java b/jme3-core/src/main/java/com/jme3/math/Spline.java index 041baa0ba..2220ea8b7 100644 --- a/jme3-core/src/main/java/com/jme3/math/Spline.java +++ b/jme3-core/src/main/java/com/jme3/math/Spline.java @@ -90,7 +90,7 @@ public class Spline implements Savable { type = splineType; this.curveTension = curveTension; this.cycle = cycle; - this.computeTotalLentgh(); + this.computeTotalLength(); } /** @@ -116,7 +116,7 @@ public class Spline implements Savable { this.controlPoints.addAll(controlPoints); this.curveTension = curveTension; this.cycle = cycle; - this.computeTotalLentgh(); + this.computeTotalLength(); } /** @@ -144,7 +144,7 @@ public class Spline implements Savable { this.weights[i] = controlPoint.w; } CurveAndSurfaceMath.prepareNurbsKnots(knots, basisFunctionDegree); - this.computeTotalLentgh(); + this.computeTotalLength(); } private void initCatmullRomWayPoints(List list) { @@ -186,7 +186,7 @@ public class Spline implements Savable { controlPoints.add(controlPoints.get(0).clone()); } if (controlPoints.size() > 1) { - this.computeTotalLentgh(); + this.computeTotalLength(); } } @@ -197,7 +197,7 @@ public class Spline implements Savable { public void removeControlPoint(Vector3f controlPoint) { controlPoints.remove(controlPoint); if (controlPoints.size() > 1) { - this.computeTotalLentgh(); + this.computeTotalLength(); } } @@ -209,7 +209,7 @@ public class Spline implements Savable { /** * This method computes the total length of the curve. */ - private void computeTotalLentgh() { + private void computeTotalLength() { totalLength = 0; float l = 0; if (segmentsLength == null) { @@ -317,7 +317,7 @@ public class Spline implements Savable { public void setCurveTension(float curveTension) { this.curveTension = curveTension; if(type==SplineType.CatmullRom && !getControlPoints().isEmpty()) { - this.computeTotalLentgh(); + this.computeTotalLength(); } } @@ -342,7 +342,7 @@ public class Spline implements Savable { controlPoints.add(controlPoints.get(0)); } this.cycle = cycle; - this.computeTotalLentgh(); + this.computeTotalLength(); } else { this.cycle = cycle; } @@ -369,7 +369,7 @@ public class Spline implements Savable { */ public void setType(SplineType type) { this.type = type; - this.computeTotalLentgh(); + this.computeTotalLength(); } /** @@ -435,9 +435,13 @@ public class Spline implements Savable { OutputCapsule oc = ex.getCapsule(this); oc.writeSavableArrayList((ArrayList) controlPoints, "controlPoints", null); oc.write(type, "type", SplineType.CatmullRom); - float list[] = new float[segmentsLength.size()]; - for (int i = 0; i < segmentsLength.size(); i++) { - list[i] = segmentsLength.get(i); + + float list[] = null; + if (segmentsLength != null) { + list = new float[segmentsLength.size()]; + for (int i = 0; i < segmentsLength.size(); i++) { + list[i] = segmentsLength.get(i); + } } oc.write(list, "segmentsLength", null); @@ -454,7 +458,7 @@ public class Spline implements Savable { public void read(JmeImporter im) throws IOException { InputCapsule in = im.getCapsule(this); - controlPoints = (ArrayList) in.readSavableArrayList("wayPoints", null); + controlPoints = (ArrayList) in.readSavableArrayList("controlPoints", new ArrayList()); /* Empty List as default, prevents null pointers */ float list[] = in.readFloatArray("segmentsLength", null); if (list != null) { segmentsLength = new ArrayList(); diff --git a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java index 2f52eb425..cf51ad0c9 100644 --- a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java +++ b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java @@ -401,8 +401,10 @@ public class FilterPostProcessor implements SceneProcessor, Savable { viewPort.getCamera().setViewPort(left, right, bottom, top); viewPort.setOutputFrameBuffer(outputBuffer); viewPort = null; - - renderFrameBuffer.dispose(); + + if(renderFrameBuffer != null){ + renderFrameBuffer.dispose(); + } if(depthTexture!=null){ depthTexture.getImage().dispose(); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java index 8287a270e..5be184c94 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java @@ -101,7 +101,7 @@ public class RenderContext { public float pointSize = 1; /** - * @see Mesh#setLineWidth(float) + * @see RenderState#setLineWidth(float) */ public float lineWidth = 1; diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java index 50e603d2f..1ad42eac9 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLRenderer.java @@ -779,6 +779,10 @@ public final class GLRenderer implements Renderer { gl.glDisable(GL.GL_STENCIL_TEST); } } + if (context.lineWidth != state.getLineWidth()) { + gl.glLineWidth(state.getLineWidth()); + context.lineWidth = state.getLineWidth(); + } } private int convertStencilOperation(StencilOperation stencilOp) { @@ -2681,8 +2685,8 @@ public final class GLRenderer implements Renderer { return; } - - if (context.lineWidth != mesh.getLineWidth()) { + //this is kept for backward compatibility. + if (mesh.getLineWidth() != -1 && context.lineWidth != mesh.getLineWidth()) { gl.glLineWidth(mesh.getLineWidth()); context.lineWidth = mesh.getLineWidth(); } diff --git a/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java index bf376006e..fd6900a97 100644 --- a/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java @@ -39,6 +39,7 @@ import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.export.binary.BinaryImporter; +import com.jme3.util.clone.Cloner; import com.jme3.util.SafeArrayList; import java.io.IOException; import java.util.*; @@ -50,7 +51,7 @@ import java.util.logging.Logger; * The AssetLinkNode does not store its children when exported to file. * Instead, you can add a list of AssetKeys that will be loaded and attached * when the AssetLinkNode is restored. - * + * * @author normenhansen */ public class AssetLinkNode extends Node { @@ -70,6 +71,18 @@ public class AssetLinkNode extends Node { assetLoaderKeys.add(key); } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + // This is a change in behavior because the old version did not clone + // this list... changes to one clone would be reflected in all. + // I think that's probably undesirable. -pspeed + this.assetLoaderKeys = cloner.clone(assetLoaderKeys); + this.assetChildren = new HashMap(); + } + /** * Add a "linked" child. These are loaded from the assetManager when the * AssetLinkNode is loaded from a binary file. @@ -166,7 +179,7 @@ public class AssetLinkNode extends Node { children.add(child); assetChildren.put(modelKey, child); } else { - Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}", + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Cannot locate {0} for asset link node {1}", new Object[]{ modelKey, key }); } } diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index 26d944b88..1338b50ef 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -48,6 +48,8 @@ import com.jme3.math.Vector3f; import com.jme3.scene.mesh.IndexBuffer; import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; /** * BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph. @@ -60,7 +62,7 @@ import com.jme3.util.TempVars; * Sub geoms can be removed but it may be slower than the normal spatial removing * Sub geoms can be added after the batch() method has been called but won't be batched and will just be rendered as normal geometries. * To integrate them in the batch you have to call the batch() method again on the batchNode. - * + * * TODO normal or tangents or both looks a bit weird * TODO more automagic (batch when needed in the updateLogicalState) * @author Nehon @@ -77,7 +79,7 @@ public class BatchNode extends GeometryGroupNode { */ protected Map batchesByGeom = new HashMap(); /** - * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer + * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer */ private float[] tmpFloat; private float[] tmpFloatN; @@ -96,7 +98,7 @@ public class BatchNode extends GeometryGroupNode { public BatchNode(String name) { super(name); } - + @Override public void onTransformChange(Geometry geom) { updateSubBatch(geom); @@ -123,7 +125,7 @@ public class BatchNode extends GeometryGroupNode { protected Matrix4f getTransformMatrix(Geometry g){ return g.cachedWorldMat; } - + protected void updateSubBatch(Geometry bg) { Batch batch = batchesByGeom.get(bg); if (batch != null) { @@ -134,13 +136,13 @@ public class BatchNode extends GeometryGroupNode { FloatBuffer posBuf = (FloatBuffer) pvb.getData(); VertexBuffer nvb = mesh.getBuffer(VertexBuffer.Type.Normal); FloatBuffer normBuf = (FloatBuffer) nvb.getData(); - + VertexBuffer opvb = origMesh.getBuffer(VertexBuffer.Type.Position); FloatBuffer oposBuf = (FloatBuffer) opvb.getData(); VertexBuffer onvb = origMesh.getBuffer(VertexBuffer.Type.Normal); FloatBuffer onormBuf = (FloatBuffer) onvb.getData(); Matrix4f transformMat = getTransformMatrix(bg); - + if (mesh.getBuffer(VertexBuffer.Type.Tangent) != null) { VertexBuffer tvb = mesh.getBuffer(VertexBuffer.Type.Tangent); @@ -184,12 +186,12 @@ public class BatchNode extends GeometryGroupNode { } batches.clear(); batchesByGeom.clear(); - } + } //only reset maxVertCount if there is something new to batch if (matMap.size() > 0) { maxVertCount = 0; } - + for (Map.Entry> entry : matMap.entrySet()) { Mesh m = new Mesh(); Material material = entry.getKey(); @@ -255,7 +257,7 @@ public class BatchNode extends GeometryGroupNode { /** * recursively visit the subgraph and unbatch geometries - * @param s + * @param s */ private void unbatchSubGraph(Spatial s) { if (s instanceof Node) { @@ -269,8 +271,8 @@ public class BatchNode extends GeometryGroupNode { } } } - - + + private void gatherGeometries(Map> map, Spatial n, boolean rebatch) { if (n instanceof Geometry) { @@ -283,7 +285,7 @@ public class BatchNode extends GeometryGroupNode { } List list = map.get(g.getMaterial()); if (list == null) { - //trying to compare materials with the isEqual method + //trying to compare materials with the isEqual method for (Map.Entry> mat : map.entrySet()) { if (g.getMaterial().contentEquals(mat.getKey())) { list = mat.getValue(); @@ -331,7 +333,7 @@ public class BatchNode extends GeometryGroupNode { /** * Sets the material to the all the batches of this BatchNode * use setMaterial(Material material,int batchIndex) to set a material to a specific batch - * + * * @param material the material to use for this geometry */ @Override @@ -341,12 +343,12 @@ public class BatchNode extends GeometryGroupNode { /** * Returns the material that is used for the first batch of this BatchNode - * + * * use getMaterial(Material material,int batchIndex) to get a material from a specific batch - * + * * @return the material that is used for the first batch of this BatchNode - * - * @see #setMaterial(com.jme3.material.Material) + * + * @see #setMaterial(com.jme3.material.Material) */ public Material getMaterial() { if (!batches.isEmpty()) { @@ -359,7 +361,7 @@ public class BatchNode extends GeometryGroupNode { /** * Merges all geometries in the collection into * the output mesh. Does not take into account materials. - * + * * @param geometries * @param outMesh */ @@ -383,7 +385,7 @@ public class BatchNode extends GeometryGroupNode { maxVertCount = geom.getVertexCount(); } Mesh.Mode listMode; - float listLineWidth = 1f; + //float listLineWidth = 1f; int components; switch (geom.getMesh().getMode()) { case Points: @@ -394,7 +396,7 @@ public class BatchNode extends GeometryGroupNode { case LineStrip: case Lines: listMode = Mesh.Mode.Lines; - listLineWidth = geom.getMesh().getLineWidth(); + //listLineWidth = geom.getMesh().getLineWidth(); components = 2; break; case TriangleFan: @@ -418,7 +420,7 @@ public class BatchNode extends GeometryGroupNode { formatForBuf[vb.getBufferType().ordinal()] = vb.getFormat(); normForBuf[vb.getBufferType().ordinal()] = vb.isNormalized(); } - + maxWeights = Math.max(maxWeights, geom.getMesh().getMaxNumWeights()); if (mode != null && mode != listMode) { @@ -426,19 +428,20 @@ public class BatchNode extends GeometryGroupNode { + " primitive types: " + mode + " != " + listMode); } mode = listMode; - if (mode == Mesh.Mode.Lines) { - if (lineWidth != 1f && listLineWidth != lineWidth) { - throw new UnsupportedOperationException("When using Mesh Line mode, cannot combine meshes with different line width " - + lineWidth + " != " + listLineWidth); - } - lineWidth = listLineWidth; - } + //Not needed anymore as lineWidth is now in RenderState and will be taken into account when merging according to the material +// if (mode == Mesh.Mode.Lines) { +// if (lineWidth != 1f && listLineWidth != lineWidth) { +// throw new UnsupportedOperationException("When using Mesh Line mode, cannot combine meshes with different line width " +// + lineWidth + " != " + listLineWidth); +// } +// lineWidth = listLineWidth; +// } compsForBuf[VertexBuffer.Type.Index.ordinal()] = components; } outMesh.setMaxNumWeights(maxWeights); outMesh.setMode(mode); - outMesh.setLineWidth(lineWidth); + //outMesh.setLineWidth(lineWidth); if (totalVerts >= 65536) { // make sure we create an UnsignedInt buffer so we can fit all of the meshes formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt; @@ -585,7 +588,7 @@ public class BatchNode extends GeometryGroupNode { int offset = start * 3; int tanOffset = start * 4; - + bindBufPos.rewind(); bindBufNorm.rewind(); bindBufTangents.rewind(); @@ -661,10 +664,10 @@ public class BatchNode extends GeometryGroupNode { vars.release(); } - protected class Batch { + protected class Batch implements JmeCloneable { /** * update the batchesByGeom map for this batch with the given List of geometries - * @param list + * @param list */ void updateGeomList(List list) { for (Geometry geom : list) { @@ -674,6 +677,25 @@ public class BatchNode extends GeometryGroupNode { } } Geometry geometry; + + public final Geometry getGeometry() { + return geometry; + } + + @Override + public Batch jmeClone() { + try { + return (Batch)super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.geometry = cloner.clone(geometry); + } + } protected void setNeedsFullRebatch(boolean needsFullRebatch) { @@ -699,7 +721,25 @@ public class BatchNode extends GeometryGroupNode { } return clone; } - + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.batches = cloner.clone(batches); + this.tmpFloat = cloner.clone(tmpFloat); + this.tmpFloatN = cloner.clone(tmpFloatN); + this.tmpFloatT = cloner.clone(tmpFloatT); + + + HashMap newBatchesByGeom = new HashMap(); + for( Map.Entry e : batchesByGeom.entrySet() ) { + newBatchesByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue())); + } + this.batchesByGeom = newBatchesByGeom; + } + @Override public int collideWith(Collidable other, CollisionResults results) { int total = 0; diff --git a/jme3-core/src/main/java/com/jme3/scene/CameraNode.java b/jme3-core/src/main/java/com/jme3/scene/CameraNode.java index 36cde482c..11de0c3c0 100644 --- a/jme3-core/src/main/java/com/jme3/scene/CameraNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/CameraNode.java @@ -36,6 +36,7 @@ import com.jme3.export.JmeImporter; import com.jme3.renderer.Camera; import com.jme3.scene.control.CameraControl; import com.jme3.scene.control.CameraControl.ControlDirection; +import com.jme3.util.clone.Cloner; import java.io.IOException; /** @@ -93,7 +94,18 @@ public class CameraNode extends Node { // this.lookAt(position, upVector); // camControl.getCamera().lookAt(position, upVector); // } - + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + // A change in behavior... I think previously CameraNode was probably + // not really cloneable... or at least its camControl would be pointing + // to the wrong control. -pspeed + this.camControl = cloner.clone(camControl); + } + @Override public void read(JmeImporter im) throws IOException { super.read(im); diff --git a/jme3-core/src/main/java/com/jme3/scene/Geometry.java b/jme3-core/src/main/java/com/jme3/scene/Geometry.java index 87f9d7147..827b1603b 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -43,6 +43,7 @@ import com.jme3.material.Material; import com.jme3.math.Matrix4f; import com.jme3.renderer.Camera; import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.clone.Cloner; import com.jme3.util.TempVars; import java.io.IOException; import java.util.Queue; @@ -54,12 +55,12 @@ import java.util.logging.Logger; * contains the geometric data for rendering objects. It manages all rendering * information such as a {@link Material} object to define how the surface * should be shaded and the {@link Mesh} data to contain the actual geometry. - * + * * @author Kirill Vainer */ public class Geometry extends Spatial { - // Version #1: removed shared meshes. + // Version #1: removed shared meshes. // models loaded with shared mesh will be automatically fixed. public static final int SAVABLE_VERSION = 1; private static final Logger logger = Logger.getLogger(Geometry.class.getName()); @@ -71,19 +72,19 @@ public class Geometry extends Spatial { */ protected boolean ignoreTransform = false; protected transient Matrix4f cachedWorldMat = new Matrix4f(); - + /** * Specifies which {@link GeometryGroupNode} this Geometry * is managed by. */ protected GeometryGroupNode groupNode; - + /** - * The start index of this Geometry's inside + * The start index of this Geometry's inside * the {@link GeometryGroupNode}. */ protected int startIndex = -1; - + /** * Serialization only. Do not use. */ @@ -95,37 +96,37 @@ public class Geometry extends Spatial { * Create a geometry node without any mesh data. * Both the mesh and the material are null, the geometry * cannot be rendered until those are set. - * + * * @param name The name of this geometry */ public Geometry(String name) { super(name); - + // For backwards compatibility, only clear the "requires // update" flag if we are not a subclass of Geometry. // This prevents subclass from silently failing to receive // updates when they upgrade. - setRequiresUpdates(Geometry.class != getClass()); + setRequiresUpdates(Geometry.class != getClass()); } /** * Create a geometry node with mesh data. * The material of the geometry is null, it cannot * be rendered until it is set. - * + * * @param name The name of this geometry * @param mesh The mesh data for this geometry */ public Geometry(String name, Mesh mesh) { this(name); - + if (mesh == null) { throw new IllegalArgumentException("mesh cannot be null"); } this.mesh = mesh; } - + @Override public boolean checkCulling(Camera cam) { if (isGrouped()) { @@ -137,8 +138,8 @@ public class Geometry extends Spatial { /** * @return If ignoreTransform mode is set. - * - * @see Geometry#setIgnoreTransform(boolean) + * + * @see Geometry#setIgnoreTransform(boolean) */ public boolean isIgnoreTransform() { return ignoreTransform; @@ -156,7 +157,7 @@ public class Geometry extends Spatial { * Level 0 indicates that the default index buffer should be used, * levels [1, LodLevels + 1] represent the levels set on the mesh * with {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }. - * + * * @param lod The lod level to set */ @Override @@ -170,7 +171,7 @@ public class Geometry extends Spatial { } lodLevel = lod; - + if (isGrouped()) { groupNode.onMeshChange(this); } @@ -178,7 +179,7 @@ public class Geometry extends Spatial { /** * Returns the LOD level set with {@link #setLodLevel(int) }. - * + * * @return the LOD level set */ public int getLodLevel() { @@ -187,10 +188,10 @@ public class Geometry extends Spatial { /** * Returns this geometry's mesh vertex count. - * + * * @return this geometry's mesh vertex count. - * - * @see Mesh#getVertexCount() + * + * @see Mesh#getVertexCount() */ public int getVertexCount() { return mesh.getVertexCount(); @@ -198,10 +199,10 @@ public class Geometry extends Spatial { /** * Returns this geometry's mesh triangle count. - * + * * @return this geometry's mesh triangle count. - * - * @see Mesh#getTriangleCount() + * + * @see Mesh#getTriangleCount() */ public int getTriangleCount() { return mesh.getTriangleCount(); @@ -209,9 +210,9 @@ public class Geometry extends Spatial { /** * Sets the mesh to use for this geometry when rendering. - * + * * @param mesh the mesh to use for this geometry - * + * * @throws IllegalArgumentException If mesh is null */ public void setMesh(Mesh mesh) { @@ -221,7 +222,7 @@ public class Geometry extends Spatial { this.mesh = mesh; setBoundRefresh(); - + if (isGrouped()) { groupNode.onMeshChange(this); } @@ -229,10 +230,10 @@ public class Geometry extends Spatial { /** * Returns the mesh to use for this geometry - * + * * @return the mesh to use for this geometry - * - * @see #setMesh(com.jme3.scene.Mesh) + * + * @see #setMesh(com.jme3.scene.Mesh) */ public Mesh getMesh() { return mesh; @@ -240,13 +241,13 @@ public class Geometry extends Spatial { /** * Sets the material to use for this geometry. - * + * * @param material the material to use for this geometry */ @Override public void setMaterial(Material material) { this.material = material; - + if (isGrouped()) { groupNode.onMaterialChange(this); } @@ -254,10 +255,10 @@ public class Geometry extends Spatial { /** * Returns the material that is used for this geometry. - * + * * @return the material that is used for this geometry - * - * @see #setMaterial(com.jme3.material.Material) + * + * @see #setMaterial(com.jme3.material.Material) */ public Material getMaterial() { return material; @@ -310,9 +311,9 @@ public class Geometry extends Spatial { computeWorldMatrix(); if (isGrouped()) { - groupNode.onTransformChange(this); + groupNode.onTransformChange(this); } - + // geometry requires lights to be sorted worldLights.sort(true); } @@ -326,9 +327,9 @@ public class Geometry extends Spatial { /** * Associate this Geometry with a {@link GeometryGroupNode}. - * + * * Should only be called by the parent {@link GeometryGroupNode}. - * + * * @param node Which {@link GeometryGroupNode} to associate with. * @param startIndex The starting index of this geometry in the group. */ @@ -336,26 +337,26 @@ public class Geometry extends Spatial { if (isGrouped()) { unassociateFromGroupNode(); } - + this.groupNode = node; this.startIndex = startIndex; } /** - * Removes the {@link GeometryGroupNode} association from this + * Removes the {@link GeometryGroupNode} association from this * Geometry. - * + * * Should only be called by the parent {@link GeometryGroupNode}. */ public void unassociateFromGroupNode() { if (groupNode != null) { - // Once the geometry is removed + // Once the geometry is removed // from the parent, the group node needs to be updated. groupNode.onGeometryUnassociated(this); groupNode = null; - + // change the default to -1 to make error detection easier - startIndex = -1; + startIndex = -1; } } @@ -367,7 +368,7 @@ public class Geometry extends Spatial { @Override protected void setParent(Node parent) { super.setParent(parent); - + // If the geometry is managed by group node we need to unassociate. if (parent == null && isGrouped()) { unassociateFromGroupNode(); @@ -413,7 +414,7 @@ public class Geometry extends Spatial { * {@link Geometry#getWorldTransform() world transform} of this geometry. * In order to receive updated values, you must call {@link Geometry#computeWorldMatrix() } * before using this method. - * + * * @return Matrix to transform from local space to world space */ public Matrix4f getWorldMatrix() { @@ -425,7 +426,7 @@ public class Geometry extends Spatial { * This alters the bound used on the mesh as well via * {@link Mesh#setBound(com.jme3.bounding.BoundingVolume) } and * forces the world bounding volume to be recomputed. - * + * * @param modelBound The model bound to set */ @Override @@ -472,15 +473,15 @@ public class Geometry extends Spatial { } /** - * Determine whether this Geometry is managed by a + * Determine whether this Geometry is managed by a * {@link GeometryGroupNode} or not. - * + * * @return True if managed by a {@link GeometryGroupNode}. */ public boolean isGrouped() { return groupNode != null; } - + /** * @deprecated Use {@link #isGrouped()} instead. */ @@ -499,14 +500,14 @@ public class Geometry extends Spatial { @Override public Geometry clone(boolean cloneMaterial) { Geometry geomClone = (Geometry) super.clone(cloneMaterial); - + // This geometry is managed, // but the cloned one is not attached to anything, hence not managed. if (geomClone.isGrouped()) { geomClone.groupNode = null; geomClone.startIndex = -1; } - + geomClone.cachedWorldMat = cachedWorldMat.clone(); if (material != null) { if (cloneMaterial) { @@ -546,6 +547,16 @@ public class Geometry extends Spatial { return geomClone; } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.mesh = cloner.clone(mesh); + this.material = cloner.clone(material); + this.groupNode = cloner.clone(groupNode); + } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); diff --git a/jme3-core/src/main/java/com/jme3/scene/LightNode.java b/jme3-core/src/main/java/com/jme3/scene/LightNode.java index fea63bf57..1a87d11b4 100644 --- a/jme3-core/src/main/java/com/jme3/scene/LightNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/LightNode.java @@ -36,11 +36,12 @@ import com.jme3.export.JmeImporter; import com.jme3.light.Light; import com.jme3.scene.control.LightControl; import com.jme3.scene.control.LightControl.ControlDirection; +import com.jme3.util.clone.Cloner; import java.io.IOException; /** * LightNode is used to link together a {@link Light} object - * with a {@link Node} object. + * with a {@link Node} object. * * @author Tim8Dev */ @@ -66,7 +67,7 @@ public class LightNode extends Node { /** * Enable or disable the LightNode functionality. - * + * * @param enabled If false, the functionality of LightNode will * be disabled. */ @@ -93,7 +94,18 @@ public class LightNode extends Node { public Light getLight() { return lightControl.getLight(); } - + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + // A change in behavior... I think previously LightNode was probably + // not really cloneable... or at least its lightControl would be pointing + // to the wrong control. -pspeed + this.lightControl = cloner.clone(lightControl); + } + @Override public void read(JmeImporter im) throws IOException { super.read(im); diff --git a/jme3-core/src/main/java/com/jme3/scene/Mesh.java b/jme3-core/src/main/java/com/jme3/scene/Mesh.java index a0f8e1fe6..f9b66bc45 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Mesh.java +++ b/jme3-core/src/main/java/com/jme3/scene/Mesh.java @@ -37,6 +37,7 @@ import com.jme3.collision.Collidable; import com.jme3.collision.CollisionResults; import com.jme3.collision.bih.BIHTree; import com.jme3.export.*; +import com.jme3.material.Material; import com.jme3.material.RenderState; import com.jme3.math.Matrix4f; import com.jme3.math.Triangle; @@ -50,6 +51,8 @@ import com.jme3.util.BufferUtils; import com.jme3.util.IntMap; import com.jme3.util.IntMap.Entry; import com.jme3.util.SafeArrayList; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.nio.*; import java.util.ArrayList; @@ -60,18 +63,18 @@ import java.util.ArrayList; * All visible elements in a scene are represented by meshes. * Meshes may contain three types of geometric primitives: *
    - *
  • Points - Every vertex represents a single point in space, + *
  • Points - Every vertex represents a single point in space, * the size of each point is specified via {@link Mesh#setPointSize(float) }. * Points can also be used for {@link RenderState#setPointSprite(boolean) point * sprite} mode.
  • *
  • Lines - 2 vertices represent a line segment, with the width specified - * via {@link Mesh#setLineWidth(float) }.
  • + * via {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)}. *
  • Triangles - 3 vertices represent a solid triangle primitive.
  • *
- * + * * @author Kirill Vainer */ -public class Mesh implements Savable, Cloneable { +public class Mesh implements Savable, Cloneable, JmeCloneable { /** * The mode of the Mesh specifies both the type of primitive represented @@ -79,59 +82,59 @@ public class Mesh implements Savable, Cloneable { */ public enum Mode { /** - * A primitive is a single point in space. The size of the points + * A primitive is a single point in space. The size of the points * can be specified with {@link Mesh#setPointSize(float) }. */ Points(true), - + /** * A primitive is a line segment. Every two vertices specify - * a single line. {@link Mesh#setLineWidth(float) } can be used + * a single line. {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can be used * to set the width of the lines. */ Lines(true), - + /** * A primitive is a line segment. The first two vertices specify - * a single line, while subsequent vertices are combined with the - * previous vertex to make a line. {@link Mesh#setLineWidth(float) } can + * a single line, while subsequent vertices are combined with the + * previous vertex to make a line. {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can * be used to set the width of the lines. */ LineStrip(false), - + /** * Identical to {@link #LineStrip} except that at the end * the last vertex is connected with the first to form a line. - * {@link Mesh#setLineWidth(float) } can be used + * {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can be used * to set the width of the lines. */ LineLoop(false), - + /** * A primitive is a triangle. Each 3 vertices specify a single * triangle. */ Triangles(true), - + /** - * Similar to {@link #Triangles}, the first 3 vertices + * Similar to {@link #Triangles}, the first 3 vertices * specify a triangle, while subsequent vertices are combined with - * the previous two to form a triangle. + * the previous two to form a triangle. */ TriangleStrip(false), - + /** - * Similar to {@link #Triangles}, the first 3 vertices + * Similar to {@link #Triangles}, the first 3 vertices * specify a triangle, each 2 subsequent vertices are combined * with the very first vertex to make a triangle. */ TriangleFan(false), - + /** * A combination of various triangle modes. It is best to avoid * using this mode as it may not be supported by all renderers. * The {@link Mesh#setModeStart(int[]) mode start points} and - * {@link Mesh#setElementLengths(int[]) element lengths} must + * {@link Mesh#setElementLengths(int[]) element lengths} must * be specified for this mode. */ Hybrid(false), @@ -141,18 +144,18 @@ public class Mesh implements Savable, Cloneable { */ Patch(true); private boolean listMode = false; - + private Mode(boolean listMode){ this.listMode = listMode; } - + /** * Returns true if the specified mode is a list mode (meaning - * ,it specifies the indices as a linear list and not some special + * ,it specifies the indices as a linear list and not some special * format). * Will return true for the types {@link #Points}, {@link #Lines} and * {@link #Triangles}. - * + * * @return true if the mode is a list type mode */ public boolean isListMode(){ @@ -172,7 +175,7 @@ public class Mesh implements Savable, Cloneable { private IntMap buffers = new IntMap(); private VertexBuffer[] lodLevels; private float pointSize = 1; - private float lineWidth = 1; + private float lineWidth = -1; private transient int vertexArrayID = -1; @@ -197,7 +200,7 @@ public class Mesh implements Savable, Cloneable { * Create a shallow clone of this Mesh. The {@link VertexBuffer vertex * buffers} are shared between this and the clone mesh, the rest * of the data is cloned. - * + * * @return A shallow clone of the mesh */ @Override @@ -222,10 +225,10 @@ public class Mesh implements Savable, Cloneable { } /** - * Creates a deep clone of this mesh. + * Creates a deep clone of this mesh. * The {@link VertexBuffer vertex buffers} and the data inside them * is cloned. - * + * * @return a deep clone of this mesh. */ public Mesh deepClone(){ @@ -244,16 +247,16 @@ public class Mesh implements Savable, Cloneable { clone.buffers.put(vb.getBufferType().ordinal(), bufClone); clone.buffersList.add(bufClone); } - + clone.vertexArrayID = -1; clone.vertCount = vertCount; clone.elementCount = elementCount; clone.instanceCount = instanceCount; - + // although this could change // if the bone weight/index buffers are modified - clone.maxNumWeights = maxNumWeights; - + clone.maxNumWeights = maxNumWeights; + clone.elementLengths = elementLengths != null ? elementLengths.clone() : null; clone.modeStart = modeStart != null ? modeStart.clone() : null; return clone; @@ -268,14 +271,14 @@ public class Mesh implements Savable, Cloneable { * of the {@link VertexBuffer vertex buffer} data, however the * {@link Type#Position}, {@link Type#Normal}, and {@link Type#Tangent} buffers * are deeply cloned. - * + * * @return A clone of the mesh for animation use. */ public Mesh cloneForAnim(){ Mesh clone = clone(); if (getBuffer(Type.BindPosePosition) != null){ VertexBuffer oldPos = getBuffer(Type.Position); - + // NOTE: creates deep clone VertexBuffer newPos = oldPos.clone(); clone.clearBuffer(Type.Position); @@ -286,7 +289,7 @@ public class Mesh implements Savable, Cloneable { VertexBuffer newNorm = oldNorm.clone(); clone.clearBuffer(Type.Normal); clone.setBuffer(newNorm); - + if (getBuffer(Type.BindPoseTangent) != null){ VertexBuffer oldTang = getBuffer(Type.Tangent); VertexBuffer newTang = oldTang.clone(); @@ -298,14 +301,43 @@ public class Mesh implements Savable, Cloneable { return clone; } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public Mesh jmeClone() { + try { + return (Mesh)super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + + // Probably could clone this now but it will get regenerated anyway. + this.collisionTree = null; + + this.meshBound = cloner.clone(meshBound); + this.buffersList = cloner.clone(buffersList); + this.buffers = cloner.clone(buffers); + this.lodLevels = cloner.clone(lodLevels); + this.elementLengths = cloner.clone(elementLengths); + this.modeStart = cloner.clone(modeStart); + } + /** * Generates the {@link Type#BindPosePosition}, {@link Type#BindPoseNormal}, - * and {@link Type#BindPoseTangent} + * and {@link Type#BindPoseTangent} * buffers for this mesh by duplicating them based on the position and normal * buffers already set on the mesh. * This method does nothing if the mesh has no bone weight or index * buffers. - * + * * @param forSoftwareAnim Should be true if the bind pose is to be generated. */ public void generateBindPose(boolean forSoftwareAnim){ @@ -338,7 +370,7 @@ public class Mesh implements Savable, Cloneable { setBuffer(bindNorm); norm.setUsage(Usage.Stream); } - + VertexBuffer tangents = getBuffer(Type.Tangent); if (tangents != null) { VertexBuffer bindTangents = new VertexBuffer(Type.BindPoseTangent); @@ -354,32 +386,32 @@ public class Mesh implements Savable, Cloneable { /** * Prepares the mesh for software skinning by converting the bone index - * and weight buffers to heap buffers. - * + * and weight buffers to heap buffers. + * * @param forSoftwareAnim Should be true to enable the conversion. */ public void prepareForAnim(boolean forSoftwareAnim){ if (forSoftwareAnim) { - // convert indices to ubytes on the heap - VertexBuffer indices = getBuffer(Type.BoneIndex); - if (!indices.getData().hasArray()) { - ByteBuffer originalIndex = (ByteBuffer) indices.getData(); - ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity()); - originalIndex.clear(); - arrayIndex.put(originalIndex); - indices.updateData(arrayIndex); - } - indices.setUsage(Usage.CpuOnly); - - // convert weights on the heap - VertexBuffer weights = getBuffer(Type.BoneWeight); - if (!weights.getData().hasArray()) { - FloatBuffer originalWeight = (FloatBuffer) weights.getData(); - FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity()); - originalWeight.clear(); - arrayWeight.put(originalWeight); - weights.updateData(arrayWeight); - } + // convert indices to ubytes on the heap + VertexBuffer indices = getBuffer(Type.BoneIndex); + if (!indices.getData().hasArray()) { + ByteBuffer originalIndex = (ByteBuffer) indices.getData(); + ByteBuffer arrayIndex = ByteBuffer.allocate(originalIndex.capacity()); + originalIndex.clear(); + arrayIndex.put(originalIndex); + indices.updateData(arrayIndex); + } + indices.setUsage(Usage.CpuOnly); + + // convert weights on the heap + VertexBuffer weights = getBuffer(Type.BoneWeight); + if (!weights.getData().hasArray()) { + FloatBuffer originalWeight = (FloatBuffer) weights.getData(); + FloatBuffer arrayWeight = FloatBuffer.allocate(originalWeight.capacity()); + originalWeight.clear(); + arrayWeight.put(originalWeight); + weights.updateData(arrayWeight); + } weights.setUsage(Usage.CpuOnly); // position, normal, and tanget buffers to be in "Stream" mode VertexBuffer positions = getBuffer(Type.Position); @@ -394,7 +426,7 @@ public class Mesh implements Savable, Cloneable { } } else { //if HWBoneIndex and HWBoneWeight are empty, we setup them as direct - //buffers with software anim buffers data + //buffers with software anim buffers data VertexBuffer indicesHW = getBuffer(Type.HWBoneIndex); if (indicesHW.getData() == null) { VertexBuffer indices = getBuffer(Type.BoneIndex); @@ -404,7 +436,7 @@ public class Mesh implements Savable, Cloneable { directIndex.put(originalIndex); indicesHW.setupData(Usage.Static, indices.getNumComponents(), indices.getFormat(), directIndex); } - + VertexBuffer weightsHW = getBuffer(Type.HWBoneWeight); if (weightsHW.getData() == null) { VertexBuffer weights = getBuffer(Type.BoneWeight); @@ -414,26 +446,26 @@ public class Mesh implements Savable, Cloneable { directWeight.put(originalWeight); weightsHW.setupData(Usage.Static, weights.getNumComponents(), weights.getFormat(), directWeight); } - + // position, normal, and tanget buffers to be in "Static" mode VertexBuffer positions = getBuffer(Type.Position); VertexBuffer normals = getBuffer(Type.Normal); VertexBuffer tangents = getBuffer(Type.Tangent); - + VertexBuffer positionsBP = getBuffer(Type.BindPosePosition); VertexBuffer normalsBP = getBuffer(Type.BindPoseNormal); VertexBuffer tangentsBP = getBuffer(Type.BindPoseTangent); - + positions.setUsage(Usage.Static); positionsBP.copyElements(0, positions, 0, positionsBP.getNumElements()); positions.setUpdateNeeded(); - + if (normals != null) { normals.setUsage(Usage.Static); normalsBP.copyElements(0, normals, 0, normalsBP.getNumElements()); normals.setUpdateNeeded(); } - + if (tangents != null) { tangents.setUsage(Usage.Static); tangentsBP.copyElements(0, tangents, 0, tangentsBP.getNumElements()); @@ -444,7 +476,7 @@ public class Mesh implements Savable, Cloneable { /** * Set the LOD (level of detail) index buffers on this mesh. - * + * * @param lodLevels The LOD levels to set */ public void setLodLevels(VertexBuffer[] lodLevels){ @@ -461,23 +493,23 @@ public class Mesh implements Savable, Cloneable { /** * Returns the lod level at the given index. - * + * * @param lod The lod level index, this does not include * the main index buffer. * @return The LOD index buffer at the index - * - * @throws IndexOutOfBoundsException If the index is outside of the + * + * @throws IndexOutOfBoundsException If the index is outside of the * range [0, {@link #getNumLodLevels()}]. - * - * @see #setLodLevels(com.jme3.scene.VertexBuffer[]) + * + * @see #setLodLevels(com.jme3.scene.VertexBuffer[]) */ public VertexBuffer getLodLevel(int lod){ return lodLevels[lod]; } - + /** * Get the element lengths for {@link Mode#Hybrid} mesh mode. - * + * * @return element lengths */ public int[] getElementLengths() { @@ -486,7 +518,7 @@ public class Mesh implements Savable, Cloneable { /** * Set the element lengths for {@link Mode#Hybrid} mesh mode. - * + * * @param elementLengths The element lengths to set */ public void setElementLengths(int[] elementLengths) { @@ -495,7 +527,7 @@ public class Mesh implements Savable, Cloneable { /** * Set the mode start indices for {@link Mode#Hybrid} mesh mode. - * + * * @return mode start indices */ public int[] getModeStart() { @@ -511,10 +543,10 @@ public class Mesh implements Savable, Cloneable { /** * Returns the mesh mode - * + * * @return the mesh mode - * - * @see #setMode(com.jme3.scene.Mesh.Mode) + * + * @see #setMode(com.jme3.scene.Mesh.Mode) */ public Mode getMode() { return mode; @@ -522,9 +554,9 @@ public class Mesh implements Savable, Cloneable { /** * Change the Mesh's mode. By default the mode is {@link Mode#Triangles}. - * + * * @param mode The new mode to set - * + * * @see Mode */ public void setMode(Mode mode) { @@ -534,10 +566,10 @@ public class Mesh implements Savable, Cloneable { /** * Returns the maximum number of weights per vertex on this mesh. - * + * * @return maximum number of weights per vertex - * - * @see #setMaxNumWeights(int) + * + * @see #setMaxNumWeights(int) */ public int getMaxNumWeights() { return maxNumWeights; @@ -547,8 +579,8 @@ public class Mesh implements Savable, Cloneable { * Set the maximum number of weights per vertex on this mesh. * Only relevant if this mesh has bone index/weight buffers. * This value should be between 0 and 4. - * - * @param maxNumWeights + * + * @param maxNumWeights */ public void setMaxNumWeights(int maxNumWeights) { this.maxNumWeights = maxNumWeights; @@ -556,23 +588,23 @@ public class Mesh implements Savable, Cloneable { /** * Returns the size of points for point meshes - * + * * @return the size of points - * - * @see #setPointSize(float) + * + * @see #setPointSize(float) */ public float getPointSize() { return pointSize; } /** - * Set the size of points for meshes of mode {@link Mode#Points}. + * Set the size of points for meshes of mode {@link Mode#Points}. * The point size is specified as on-screen pixels, the default * value is 1.0. The point size * does nothing if {@link RenderState#setPointSprite(boolean) point sprite} - * render state is enabled, in that case, the vertex shader must specify the + * render state is enabled, in that case, the vertex shader must specify the * point size by writing to gl_PointSize. - * + * * @param pointSize The size of points */ public void setPointSize(float pointSize) { @@ -581,26 +613,30 @@ public class Mesh implements Savable, Cloneable { /** * Returns the line width for line meshes. - * + * * @return the line width + * @deprecated use {@link Material#getAdditionalRenderState()} and {@link RenderState#getLineWidth()} */ + @Deprecated public float getLineWidth() { return lineWidth; } /** * Specify the line width for meshes of the line modes, such - * as {@link Mode#Lines}. The line width is specified as on-screen pixels, + * as {@link Mode#Lines}. The line width is specified as on-screen pixels, * the default value is 1.0. - * + * * @param lineWidth The line width + * @deprecated use {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} */ + @Deprecated public void setLineWidth(float lineWidth) { this.lineWidth = lineWidth; } /** - * Indicates to the GPU that this mesh will not be modified (a hint). + * Indicates to the GPU that this mesh will not be modified (a hint). * Sets the usage mode to {@link Usage#Static} * for all {@link VertexBuffer vertex buffers} on this Mesh. */ @@ -641,7 +677,7 @@ public class Mesh implements Savable, Cloneable { public void setInterleaved(){ ArrayList vbs = new ArrayList(); vbs.addAll(buffersList); - + // ArrayList vbs = new ArrayList(buffers.values()); // index buffer not included when interleaving vbs.remove(getBuffer(Type.Index)); @@ -660,7 +696,7 @@ public class Mesh implements Savable, Cloneable { VertexBuffer allData = new VertexBuffer(Type.InterleavedData); ByteBuffer dataBuf = BufferUtils.createByteBuffer(stride * getVertexCount()); allData.setupData(Usage.Static, 1, Format.UnsignedByte, dataBuf); - + // adding buffer directly so that no update counts is forced buffers.put(Type.InterleavedData.ordinal(), allData); buffersList.add(allData); @@ -711,7 +747,7 @@ public class Mesh implements Savable, Cloneable { for (VertexBuffer vb : vbs){ vb.setOffset(offset); vb.setStride(stride); - + vb.updateData(null); //vb.setupData(vb.usage, vb.components, vb.format, null); offset += vb.componentsLength; @@ -745,20 +781,20 @@ public class Mesh implements Savable, Cloneable { int max = 0; for( VertexBuffer vb : buffersList ) { if( vb.getBaseInstanceCount() > max ) { - max = vb.getBaseInstanceCount(); - } - } + max = vb.getBaseInstanceCount(); + } + } return max; } /** - * Update the {@link #getVertexCount() vertex} and + * Update the {@link #getVertexCount() vertex} and * {@link #getTriangleCount() triangle} counts for this mesh * based on the current data. This method should be called * after the {@link Buffer#capacity() capacities} of the mesh's * {@link VertexBuffer vertex buffers} has been altered. - * - * @throws IllegalStateException If this mesh is in + * + * @throws IllegalStateException If this mesh is in * {@link #setInterleaved() interleaved} format. */ public void updateCounts(){ @@ -774,13 +810,13 @@ public class Mesh implements Savable, Cloneable { elementCount = computeNumElements(ib.getData().limit()); }else{ elementCount = computeNumElements(vertCount); - } + } instanceCount = computeInstanceCount(); } /** * Returns the triangle count for the given LOD level. - * + * * @param lod The lod level to look up * @return The triangle count for that LOD level */ @@ -803,10 +839,10 @@ public class Mesh implements Savable, Cloneable { /** * Returns how many triangles or elements are on this Mesh. * This value is only updated when {@link #updateCounts() } is called. - * If the mesh mode is not a triangle mode, then this returns the + * If the mesh mode is not a triangle mode, then this returns the * number of elements/primitives, e.g. how many lines or how many points, * instead of how many triangles. - * + * * @return how many triangles/elements are on this Mesh. */ public int getTriangleCount(){ @@ -815,30 +851,30 @@ public class Mesh implements Savable, Cloneable { /** * Returns the number of vertices on this mesh. - * The value is computed based on the position buffer, which + * The value is computed based on the position buffer, which * must be set on all meshes. - * + * * @return Number of vertices on the mesh */ public int getVertexCount(){ return vertCount; } - + /** * Returns the number of instances this mesh contains. The instance * count is based on any VertexBuffers with instancing set. */ public int getInstanceCount() { return instanceCount; - } + } /** - * Gets the triangle vertex positions at the given triangle index + * Gets the triangle vertex positions at the given triangle index * and stores them into the v1, v2, v3 arguments. - * - * @param index The index of the triangle. + * + * @param index The index of the triangle. * Should be between 0 and {@link #getTriangleCount()}. - * + * * @param v1 Vector to contain first vertex position * @param v2 Vector to contain second vertex position * @param v3 Vector to contain third vertex position @@ -863,15 +899,15 @@ public class Mesh implements Savable, Cloneable { + " has incompatible format"); } } - + /** - * Gets the triangle vertex positions at the given triangle index + * Gets the triangle vertex positions at the given triangle index * and stores them into the {@link Triangle} argument. * Also sets the triangle index to the index argument. - * - * @param index The index of the triangle. + * + * @param index The index of the triangle. * Should be between 0 and {@link #getTriangleCount()}. - * + * * @param tri The triangle to store the positions in */ public void getTriangle(int index, Triangle tri){ @@ -881,12 +917,12 @@ public class Mesh implements Savable, Cloneable { } /** - * Gets the triangle vertex indices at the given triangle index + * Gets the triangle vertex indices at the given triangle index * and stores them into the given int array. - * - * @param index The index of the triangle. + * + * @param index The index of the triangle. * Should be between 0 and {@link #getTriangleCount()}. - * + * * @param indices Indices of the triangle's vertices */ public void getTriangle(int index, int[] indices){ @@ -912,15 +948,15 @@ public class Mesh implements Savable, Cloneable { public void setId(int id){ if (vertexArrayID != -1) throw new IllegalStateException("ID has already been set."); - + vertexArrayID = id; } /** * Generates a collision tree for the mesh. - * Called automatically by {@link #collideWith(com.jme3.collision.Collidable, - * com.jme3.math.Matrix4f, - * com.jme3.bounding.BoundingVolume, + * Called automatically by {@link #collideWith(com.jme3.collision.Collidable, + * com.jme3.math.Matrix4f, + * com.jme3.bounding.BoundingVolume, * com.jme3.collision.CollisionResults) }. */ public void createCollisionData(){ @@ -943,7 +979,7 @@ public class Mesh implements Savable, Cloneable { * User code should only use collideWith() on scene * graph elements such as {@link Spatial}s. */ - public int collideWith(Collidable other, + public int collideWith(Collidable other, Matrix4f worldMatrix, BoundingVolume worldBound, CollisionResults results){ @@ -951,18 +987,18 @@ public class Mesh implements Savable, Cloneable { if (getVertexCount() == 0) { return 0; } - + if (collisionTree == null){ createCollisionData(); } - + return collisionTree.collideWith(other, worldMatrix, worldBound, results); } /** * Sets the {@link VertexBuffer} on the mesh. * This will update the vertex/triangle counts if needed. - * + * * @param vb The buffer to set * @throws IllegalArgumentException If the buffer type is already set */ @@ -974,12 +1010,12 @@ public class Mesh implements Savable, Cloneable { buffersList.add(vb); updateCounts(); } - + /** * Unsets the {@link VertexBuffer} set on this mesh - * with the given type. Does nothing if the vertex buffer type is not set + * with the given type. Does nothing if the vertex buffer type is not set * initially. - * + * * @param type The buffer type to remove */ public void clearBuffer(VertexBuffer.Type type){ @@ -989,17 +1025,17 @@ public class Mesh implements Savable, Cloneable { updateCounts(); } } - + /** * Creates a {@link VertexBuffer} for the mesh or modifies * the existing one per the parameters given. - * + * * @param type The type of the buffer * @param components Number of components * @param format Data format * @param buf The buffer data - * - * @throws UnsupportedOperationException If the buffer already set is + * + * @throws UnsupportedOperationException If the buffer already set is * incompatible with the parameters given. */ public void setBuffer(Type type, int components, Format format, Buffer buf){ @@ -1017,16 +1053,16 @@ public class Mesh implements Savable, Cloneable { updateCounts(); } } - + /** - * Set a floating point {@link VertexBuffer} on the mesh. - * - * @param type The type of {@link VertexBuffer}, + * Set a floating point {@link VertexBuffer} on the mesh. + * + * @param type The type of {@link VertexBuffer}, * e.g. {@link Type#Position}, {@link Type#Normal}, etc. - * + * * @param components Number of components on the vertex buffer, should * be between 1 and 4. - * + * * @param buf The floating point data to contain */ public void setBuffer(Type type, int components, FloatBuffer buf) { @@ -1064,9 +1100,9 @@ public class Mesh implements Savable, Cloneable { /** * Get the {@link VertexBuffer} stored on this mesh with the given * type. - * + * * @param type The type of VertexBuffer - * @return the VertexBuffer data, or null if not set + * @return the VertexBuffer data, or null if not set */ public VertexBuffer getBuffer(Type type){ return buffers.get(type.ordinal()); @@ -1075,7 +1111,7 @@ public class Mesh implements Savable, Cloneable { /** * Get the {@link VertexBuffer} data stored on this mesh in float * format. - * + * * @param type The type of VertexBuffer * @return the VertexBuffer data, or null if not set */ @@ -1086,11 +1122,11 @@ public class Mesh implements Savable, Cloneable { return (FloatBuffer) vb.getData(); } - + /** * Get the {@link VertexBuffer} data stored on this mesh in short * format. - * + * * @param type The type of VertexBuffer * @return the VertexBuffer data, or null if not set */ @@ -1105,18 +1141,18 @@ public class Mesh implements Savable, Cloneable { /** * Acquires an index buffer that will read the vertices on the mesh * as a list. - * + * * @return A virtual or wrapped index buffer to read the data as a list */ public IndexBuffer getIndicesAsList(){ if (mode == Mode.Hybrid) throw new UnsupportedOperationException("Hybrid mode not supported"); - + IndexBuffer ib = getIndexBuffer(); if (ib != null){ if (mode.isListMode()){ // already in list mode - return ib; + return ib; }else{ // not in list mode but it does have an index buffer // wrap it so the data is converted to list format @@ -1128,20 +1164,20 @@ public class Mesh implements Savable, Cloneable { return new VirtualIndexBuffer(vertCount, mode); } } - + /** - * Get the index buffer for this mesh. + * Get the index buffer for this mesh. * Will return null if no index buffer is set. - * + * * @return The index buffer of this mesh. - * + * * @see Type#Index */ public IndexBuffer getIndexBuffer() { VertexBuffer vb = getBuffer(Type.Index); if (vb == null) return null; - + return IndexBuffer.wrapIndexBuffer(vb.getData()); } @@ -1151,7 +1187,7 @@ public class Mesh implements Savable, Cloneable { * to index into the attributes of the other mesh. * Note that this will also change this mesh's index buffer so that * the references to the vertex data match the new indices. - * + * * @param other The mesh to extract the vertex data from */ public void extractVertexData(Mesh other) { @@ -1171,7 +1207,7 @@ public class Mesh implements Savable, Cloneable { int oldIndex = indexBuf.get(i); if (!oldIndicesToNewIndices.containsKey(oldIndex)) { - // this vertex has not been added, so allocate a + // this vertex has not been added, so allocate a // new index for it and add it to the map oldIndicesToNewIndices.put(oldIndex, newIndex); newIndicesToOldIndices.add(oldIndex); @@ -1188,8 +1224,8 @@ public class Mesh implements Savable, Cloneable { throw new AssertionError(); } - // Create the new index buffer. - // Do not overwrite the old one because we might be able to + // Create the new index buffer. + // Do not overwrite the old one because we might be able to // convert from int index buffer to short index buffer IndexBuffer newIndexBuf; if (newNumVerts >= 65536) { @@ -1205,10 +1241,10 @@ public class Mesh implements Savable, Cloneable { newIndexBuf.put(i, newIndex); } - + VertexBuffer newIdxBuf = new VertexBuffer(Type.Index); - newIdxBuf.setupData(oldIdxBuf.getUsage(), - oldIdxBuf.getNumComponents(), + newIdxBuf.setupData(oldIdxBuf.getUsage(), + oldIdxBuf.getNumComponents(), newIndexBuf instanceof IndexIntBuffer ? Format.UnsignedInt : Format.UnsignedShort, newIndexBuf.getBuffer()); clearBuffer(Type.Index); @@ -1224,7 +1260,7 @@ public class Mesh implements Savable, Cloneable { VertexBuffer newVb = new VertexBuffer(oldVb.getBufferType()); newVb.setNormalized(oldVb.isNormalized()); - //check for data before copying, some buffers are just empty shells + //check for data before copying, some buffers are just empty shells //for caching purpose (HW skinning buffers), and will be filled when //needed if(oldVb.getData()!=null){ @@ -1242,32 +1278,32 @@ public class Mesh implements Savable, Cloneable { oldVb.copyElement(oldIndex, newVb, i); } } - + // Set the buffer on the mesh clearBuffer(newVb.getBufferType()); setBuffer(newVb); } - + // Copy max weights per vertex as well setMaxNumWeights(other.getMaxNumWeights()); - + // The data has been copied over, update informations updateCounts(); updateBound(); } - + /** * Scales the texture coordinate buffer on this mesh by the given - * scale factor. + * scale factor. *

- * Note that values above 1 will cause the - * texture to tile, while values below 1 will cause the texture + * Note that values above 1 will cause the + * texture to tile, while values below 1 will cause the texture * to stretch. *

- * + * * @param scaleFactor The scale factor to scale by. Every texture * coordinate is multiplied by this vector to get the result. - * + * * @throws IllegalStateException If there's no texture coordinate * buffer on the mesh * @throws UnsupportedOperationException If the texture coordinate @@ -1299,7 +1335,7 @@ public class Mesh implements Savable, Cloneable { } /** - * Updates the bounding volume of this mesh. + * Updates the bounding volume of this mesh. * The method does nothing if the mesh has no {@link Type#Position} buffer. * It is expected that the position buffer is a float buffer with 3 components. */ @@ -1313,7 +1349,7 @@ public class Mesh implements Savable, Cloneable { /** * Returns the {@link BoundingVolume} of this Mesh. * By default the bounding volume is a {@link BoundingBox}. - * + * * @return the bounding volume of this mesh */ public BoundingVolume getBound() { @@ -1323,7 +1359,7 @@ public class Mesh implements Savable, Cloneable { /** * Sets the {@link BoundingVolume} for this Mesh. * The bounding volume is recomputed by calling {@link #updateBound() }. - * + * * @param modelBound The model bound to set */ public void setBound(BoundingVolume modelBound) { @@ -1334,38 +1370,38 @@ public class Mesh implements Savable, Cloneable { * Returns a map of all {@link VertexBuffer vertex buffers} on this Mesh. * The integer key for the map is the {@link Enum#ordinal() ordinal} * of the vertex buffer's {@link Type}. - * Note that the returned map is a reference to the map used internally, + * Note that the returned map is a reference to the map used internally, * modifying it will cause undefined results. - * + * * @return map of vertex buffers on this mesh. */ public IntMap getBuffers(){ return buffers; } - + /** * Returns a list of all {@link VertexBuffer vertex buffers} on this Mesh. * Using a list instead an IntMap via the {@link #getBuffers() } method is * better for iteration as there's no need to create an iterator instance. * Note that the returned list is a reference to the list used internally, * modifying it will cause undefined results. - * + * * @return list of vertex buffers on this mesh. */ public SafeArrayList getBufferList(){ return buffersList; } - + /** * Determines if the mesh uses bone animation. - * + * * A mesh uses bone animation if it has bone index / weight buffers * such as {@link Type#BoneIndex} or {@link Type#HWBoneIndex}. - * + * * @return true if the mesh uses bone animation, false otherwise */ public boolean isAnimated() { - return getBuffer(Type.BoneIndex) != null || + return getBuffer(Type.BoneIndex) != null || getBuffer(Type.HWBoneIndex) != null; } @@ -1405,7 +1441,7 @@ public class Mesh implements Savable, Cloneable { out.write(elementLengths, "elementLengths", null); out.write(modeStart, "modeStart", null); out.write(pointSize, "pointSize", 1f); - + //Removing HW skinning buffers to not save them VertexBuffer hwBoneIndex = null; VertexBuffer hwBoneWeight = null; @@ -1451,7 +1487,7 @@ public class Mesh implements Savable, Cloneable { for (Entry entry : buffers){ buffersList.add(entry.getValue()); } - + //creating hw animation buffers empty so that they are put in the cache if(isAnimated()){ VertexBuffer hwBoneIndex = new VertexBuffer(Type.HWBoneIndex); @@ -1461,7 +1497,7 @@ public class Mesh implements Savable, Cloneable { hwBoneWeight.setUsage(Usage.CpuOnly); setBuffer(hwBoneWeight); } - + Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null); if (lodLevelsSavable != null) { lodLevels = new VertexBuffer[lodLevelsSavable.length]; diff --git a/jme3-core/src/main/java/com/jme3/scene/Node.java b/jme3-core/src/main/java/com/jme3/scene/Node.java index f269446a5..59f0beb85 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -40,6 +40,7 @@ import com.jme3.export.Savable; import com.jme3.material.Material; import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -53,7 +54,7 @@ import java.util.logging.Logger; * node maintains a collection of children and handles merging said children * into a single bound to allow for very fast culling of multiple nodes. Node * allows for any number of children to be attached. - * + * * @author Mark Powell * @author Gregg Patton * @author Joshua Slack @@ -62,26 +63,26 @@ public class Node extends Spatial { private static final Logger logger = Logger.getLogger(Node.class.getName()); - /** + /** * This node's children. */ protected SafeArrayList children = new SafeArrayList(Spatial.class); /** * If this node is a root, this list will contain the current - * set of children (and children of children) that require + * set of children (and children of children) that require * updateLogicalState() to be called as indicated by their * requiresUpdate() method. */ private SafeArrayList updateList = null; - + /** * False if the update list requires rebuilding. This is Node.class * specific and therefore not included as part of the Spatial update flags. * A flag is used instead of nulling the updateList to avoid reallocating * a whole list every time the scene graph changes. - */ - private boolean updateListValid = false; + */ + private boolean updateListValid = false; /** * Serialization only. Do not use. @@ -93,29 +94,29 @@ public class Node extends Spatial { /** * Constructor instantiates a new Node with a default empty * list for containing children. - * + * * @param name the name of the scene element. This is required for * identification and comparison purposes. */ public Node(String name) { super(name); - + // For backwards compatibility, only clear the "requires // update" flag if we are not a subclass of Node. // This prevents subclass from silently failing to receive // updates when they upgrade. - setRequiresUpdates(Node.class != getClass()); + setRequiresUpdates(Node.class != getClass()); } /** - * + * * getQuantity returns the number of children this node * maintains. - * + * * @return the number of children this node maintains. */ public int getQuantity() { - return children.size(); + return children.size(); } @Override @@ -143,7 +144,7 @@ public class Node extends Spatial { @Override protected void updateWorldBound(){ super.updateWorldBound(); - + // for a node, the world bound is a combination of all it's children // bounds BoundingVolume resultBound = null; @@ -167,7 +168,7 @@ public class Node extends Spatial { protected void setParent(Node parent) { if( this.parent == null && parent != null ) { // We were a root before and now we aren't... make sure if - // we had an updateList then we clear it completely to + // we had an updateList then we clear it completely to // avoid holding the dead array. updateList = null; updateListValid = false; @@ -204,15 +205,15 @@ public class Node extends Spatial { return updateList; } if( updateList == null ) { - updateList = new SafeArrayList(Spatial.class); + updateList = new SafeArrayList(Spatial.class); } else { updateList.clear(); } // Build the list addUpdateChildren(updateList); - updateListValid = true; - return updateList; + updateListValid = true; + return updateList; } @Override @@ -238,7 +239,7 @@ public class Node extends Spatial { // This branch has no geometric state that requires updates. return; } - + if ((refreshFlags & RF_LIGHTLIST) != 0){ updateWorldLightList(); } @@ -250,7 +251,7 @@ public class Node extends Spatial { } refreshFlags &= ~RF_CHILD_LIGHTLIST; - + if (!children.isEmpty()) { // the important part- make sure child geometric state is refreshed // first before updating own world bound. This saves @@ -260,7 +261,7 @@ public class Node extends Spatial { for (Spatial child : children.getArray()) { child.updateGeometricState(); } - } + } if ((refreshFlags & RF_BOUND) != 0){ updateWorldBound(); @@ -272,7 +273,7 @@ public class Node extends Spatial { /** * getTriangleCount returns the number of triangles contained * in all sub-branches of this node that contain geometry. - * + * * @return the triangle count of this branch. */ @Override @@ -286,11 +287,11 @@ public class Node extends Spatial { return count; } - + /** * getVertexCount returns the number of vertices contained * in all sub-branches of this node that contain geometry. - * + * * @return the vertex count of this branch. */ @Override @@ -311,7 +312,7 @@ public class Node extends Spatial { * returned. *
* If the child already had a parent it is detached from that former parent. - * + * * @param child * the child to attach to this node. * @return the number of children maintained by this node. @@ -320,15 +321,15 @@ public class Node extends Spatial { public int attachChild(Spatial child) { return attachChildAt(child, children.size()); } - + /** - * + * * attachChildAt attaches a child to this node at an index. This node * becomes the child's parent. The current number of children maintained is * returned. *
* If the child already had a parent it is detached from that former parent. - * + * * @param child * the child to attach to this node. * @return the number of children maintained by this node. @@ -344,7 +345,7 @@ public class Node extends Spatial { } child.setParent(this); children.add(index, child); - + // XXX: Not entirely correct? Forces bound update up the // tree stemming from the attached child. Also forces // transform update down the tree- @@ -354,17 +355,17 @@ public class Node extends Spatial { logger.log(Level.FINE,"Child ({0}) attached to this node ({1})", new Object[]{child.getName(), getName()}); } - + invalidateUpdateList(); } - + return children.size(); } /** * detachChild removes a given child from the node's list. * This child will no longer be maintained. - * + * * @param child * the child to remove. * @return the index the child was at. -1 if the child was not in the list. @@ -379,16 +380,16 @@ public class Node extends Spatial { detachChildAt(index); } return index; - } - - return -1; + } + + return -1; } /** * detachChild removes a given child from the node's list. * This child will no longe be maintained. Only the first child with a * matching name is removed. - * + * * @param childName * the child to remove. * @return the index the child was at. -1 if the child was not in the list. @@ -408,10 +409,10 @@ public class Node extends Spatial { } /** - * + * * detachChildAt removes a child at a given index. That child * is returned for saving purposes. - * + * * @param index * the index of the child to be removed. * @return the child at the supplied index. @@ -432,14 +433,14 @@ public class Node extends Spatial { child.setTransformRefresh(); // lights are also inherited from parent child.setLightListRefresh(); - + invalidateUpdateList(); } return child; } /** - * + * * detachAllChildren removes all children attached to this * node. */ @@ -458,7 +459,7 @@ public class Node extends Spatial { * in this node's list of children. * @param sp * The spatial to look up - * @return + * @return * The index of the spatial in the node's children, or -1 * if the spatial is not attached to this node */ @@ -468,7 +469,7 @@ public class Node extends Spatial { /** * More efficient than e.g detaching and attaching as no updates are needed. - * + * * @param index1 The index of the first child to swap * @param index2 The index of the second child to swap */ @@ -481,9 +482,9 @@ public class Node extends Spatial { } /** - * + * * getChild returns a child at a given index. - * + * * @param i * the index to retrieve the child from. * @return the child at a specified index. @@ -497,13 +498,13 @@ public class Node extends Spatial { * given name (case sensitive.) This method does a depth first recursive * search of all descendants of this node, it will return the first spatial * found with a matching name. - * + * * @param name * the name of the child to retrieve. If null, we'll return null. * @return the child if found, or null. */ public Spatial getChild(String name) { - if (name == null) + if (name == null) return null; for (Spatial child : children.getArray()) { @@ -518,11 +519,11 @@ public class Node extends Spatial { } return null; } - + /** * determines if the provided Spatial is contained in the children list of * this node. - * + * * @param spat * the child object to look for. * @return true if the object is contained, false otherwise. @@ -566,39 +567,39 @@ public class Node extends Spatial { public int collideWith(Collidable other, CollisionResults results){ int total = 0; - + // optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children - // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all. + // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all. // The idea is when there are few children, it can be too expensive to test boundingVolume first. /* I'm removing this change until some issues can be addressed and I really think it needs to be implemented a better way anyway. - + First, it causes issues for anyone doing collideWith() with BoundingVolumes and expecting it to trickle down to the children. For example, children with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing a collision check at the parent level then has to do a BoundingSphere to BoundingBox collision which isn't resolved. (Having to come up with a collision point in that case is tricky and the first sign that this is the wrong approach.) - + Second, the rippling changes this caused to 'optimize' collideWith() for this special use-case are another sign that this approach was a bit dodgy. The whole idea of calculating a full collision just to see if the two shapes collide at all is very wasteful. - + A proper implementation should support a simpler boolean check that doesn't do all of that calculation. For example, if 'other' is also a BoundingVolume (ie: 99.9% of all non-Ray cases) then a direct BV to BV intersects() test can be done. So much faster. And if 'other' _is_ a Ray then the BV.intersects(Ray) call can be done. - + I don't have time to do it right now but I'll at least un-break a bunch of peoples' code until it can be 'optimized' properly. Hopefully it's not too late to back out - the other dodgy ripples this caused. -pspeed (hindsight-expert ;)) - + the other dodgy ripples this caused. -pspeed (hindsight-expert ;)) + Note: the code itself is relatively simple to implement but I don't have time to a) test it, and b) see if '> 4' is still a decent check for it. Could be it's fast enough to do all the time for > 1. - + if (children.size() > 4) { BoundingVolume bv = this.getWorldBound(); @@ -642,7 +643,7 @@ public class Node extends Spatial { * @return Non-null, but possibly 0-element, list of matching Spatials (also Instances extending Spatials). * * @see java.util.regex.Pattern - * @see Spatial#matches(java.lang.Class, java.lang.String) + * @see Spatial#matches(java.lang.Class, java.lang.String) */ @SuppressWarnings("unchecked") public List descendantMatches( @@ -662,7 +663,7 @@ public class Node extends Spatial { /** * Convenience wrapper. * - * @see #descendantMatches(java.lang.Class, java.lang.String) + * @see #descendantMatches(java.lang.Class, java.lang.String) */ public List descendantMatches( Class spatialSubclass) { @@ -672,7 +673,7 @@ public class Node extends Spatial { /** * Convenience wrapper. * - * @see #descendantMatches(java.lang.Class, java.lang.String) + * @see #descendantMatches(java.lang.Class, java.lang.String) */ public List descendantMatches(String nameRegex) { return descendantMatches(null, nameRegex); @@ -691,7 +692,7 @@ public class Node extends Spatial { // Reset the fields of the clone that should be in a 'new' state. nodeClone.updateList = null; nodeClone.updateListValid = false; // safe because parent is nulled out in super.clone() - + return nodeClone; } @@ -707,6 +708,19 @@ public class Node extends Spatial { return nodeClone; } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.children = cloner.clone(children); + + // Only the outer cloning thing knows whether this should be nulled + // or not... after all, we might be cloning a root node in which case + // cloning this list is fine. + this.updateList = cloner.clone(updateList); + } + @Override public void write(JmeExporter e) throws IOException { super.write(e); @@ -718,8 +732,8 @@ public class Node extends Spatial { // XXX: Load children before loading itself!! // This prevents empty children list if controls query // it in Control.setSpatial(). - - children = new SafeArrayList( Spatial.class, + + children = new SafeArrayList( Spatial.class, e.getCapsule(this).readSavableArrayList("children", null) ); // go through children and set parent to this node @@ -728,7 +742,7 @@ public class Node extends Spatial { child.parent = this; } } - + super.read(e); } @@ -749,7 +763,7 @@ public class Node extends Spatial { } } } - + @Override public void depthFirstTraversal(SceneGraphVisitor visitor) { for (Spatial child : children.getArray()) { @@ -757,7 +771,7 @@ public class Node extends Spatial { } visitor.visit(this); } - + @Override protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue queue) { queue.addAll(children); diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index 1fe68d730..f833b6758 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -47,6 +47,8 @@ import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; import java.io.IOException; @@ -63,17 +65,17 @@ import java.util.logging.Logger; * @author Joshua Slack * @version $Revision: 4075 $, $Data$ */ -public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset { +public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable { private static final Logger logger = Logger.getLogger(Spatial.class.getName()); /** - * Specifies how frustum culling should be handled by + * Specifies how frustum culling should be handled by * this spatial. */ public enum CullHint { - /** + /** * Do whatever our parent does. If no parent, default to {@link #Dynamic}. */ Inherit, @@ -83,13 +85,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * Camera planes whether or not this Spatial should be culled. */ Dynamic, - /** + /** * Always cull this from the view, throwing away this object * and any children from rendering commands. */ Always, /** - * Never cull this from view, always draw it. + * Never cull this from view, always draw it. * Note that we will still get culled if our parent is culled. */ Never; @@ -100,15 +102,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ public enum BatchHint { - /** + /** * Do whatever our parent does. If no parent, default to {@link #Always}. */ Inherit, - /** + /** * This spatial will always be batched when attached to a BatchNode. */ Always, - /** + /** * This spatial will never be batched when attached to a BatchNode. */ Never; @@ -118,12 +120,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms RF_BOUND = 0x02, - RF_LIGHTLIST = 0x04, // changes in light lists + RF_LIGHTLIST = 0x04, // changes in light lists RF_CHILD_LIGHTLIST = 0x08; // some child need geometry update - + protected CullHint cullHint = CullHint.Inherit; protected BatchHint batchHint = BatchHint.Inherit; - /** + /** * Spatial's bounding volume relative to the world. */ protected BoundingVolume worldBound; @@ -132,7 +134,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ protected LightList localLights; protected transient LightList worldLights; - /** + /** * This spatial's name. */ protected String name; @@ -147,11 +149,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab protected HashMap userData = null; /** * Used for smart asset caching - * - * @see AssetKey#useSmartCache() + * + * @see AssetKey#useSmartCache() */ protected AssetKey key; - /** + /** * Spatial's parent, or null if it has none. */ protected transient Node parent; @@ -174,7 +176,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab /** * Serialization only. Do not use. * Not really. This class is never instantiated directly but the - * subclasses like to use the no-arg constructor for their own + * subclasses like to use the no-arg constructor for their own * no-arg constructor... which is technically weaker than * forward supplying defaults. */ @@ -192,7 +194,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ protected Spatial(String name) { this.name = name; - + localTransform = new Transform(); worldTransform = new Transform(); @@ -219,13 +221,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab boolean requiresUpdates() { return requiresUpdates | !controls.isEmpty(); } - + /** - * Subclasses can call this with true to denote that they require + * Subclasses can call this with true to denote that they require * updateLogicalState() to be called even if they contain no controls. * Setting this to false reverts to the default behavior of only * updating if the spatial has controls. This is not meant to - * indicate dynamic state in any way and must be called while + * indicate dynamic state in any way and must be called while * unattached or an IllegalStateException is thrown. It is designed * to be called during object construction and then never changed, ie: * it's meant to be subclass specific state and not runtime state. @@ -251,12 +253,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab // override it for more optimal behavior. Node and Geometry will override // it to false if the class is Node.class or Geometry.class. // This means that all subclasses will default to the old behavior - // unless they opt in. + // unless they opt in. if( parent != null ) { - throw new IllegalStateException("setRequiresUpdates() cannot be called once attached."); + throw new IllegalStateException("setRequiresUpdates() cannot be called once attached."); } this.requiresUpdates = f; - } + } /** * Indicate that the transform of this spatial has changed and that @@ -269,13 +271,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab protected void setLightListRefresh() { refreshFlags |= RF_LIGHTLIST; - + // Make sure next updateGeometricState() visits this branch // to update lights. Spatial p = parent; while (p != null) { //if (p.refreshFlags != 0) { - // any refresh flag is sufficient, + // any refresh flag is sufficient, // as each propagates to the root Node // 2015/2/8: @@ -283,16 +285,16 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab // or getWorldTransform() activates a "partial refresh" // which does not update the lights but does clear // the refresh flags on the ancestors! - - // return; + + // return; //} - + if ((p.refreshFlags & RF_CHILD_LIGHTLIST) != 0) { // The parent already has this flag, // so must all ancestors. return; } - + p.refreshFlags |= RF_CHILD_LIGHTLIST; p = p.parent; } @@ -315,10 +317,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab p = p.parent; } } - + /** * (Internal use only) Forces a refresh of the given types of data. - * + * * @param transforms Refresh world transform based on parents' * @param bounds Refresh bounding volume data based on child nodes * @param lights Refresh light list based on parents' @@ -401,9 +403,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab /** * Returns the local {@link LightList}, which are the lights * that were directly attached to this Spatial through the - * {@link #addLight(com.jme3.light.Light) } and + * {@link #addLight(com.jme3.light.Light) } and * {@link #removeLight(com.jme3.light.Light) } methods. - * + * * @return The local light list */ public LightList getLocalLightList() { @@ -414,7 +416,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * Returns the world {@link LightList}, containing the lights * combined from all this Spatial's parents up to and including * this Spatial's lights. - * + * * @return The combined world light list */ public LightList getWorldLightList() { @@ -502,14 +504,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * lookAt is a convenience method for auto-setting the local * rotation based on a position in world space and an up vector. It computes the rotation * to transform the z-axis to point onto 'position' and the y-axis to 'up'. - * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) } + * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) } * this method takes a world position to look at and not a relative direction. * * Note : 28/01/2013 this method has been fixed as it was not taking into account the parent rotation. * This was resulting in improper rotation when the spatial had rotated parent nodes. - * This method is intended to work in world space, so no matter what parent graph the + * This method is intended to work in world space, so no matter what parent graph the * spatial has, it will look at the given position in world space. - * + * * @param position * where to look at in terms of world coordinates * @param upVector @@ -522,10 +524,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab TempVars vars = TempVars.get(); Vector3f compVecA = vars.vect4; - + compVecA.set(position).subtractLocal(worldTranslation); - getLocalRotation().lookAt(compVecA, upVector); - + getLocalRotation().lookAt(compVecA, upVector); + if ( getParent() != null ) { Quaternion rot=vars.quat1; rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation()); @@ -579,7 +581,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab } /** - * Computes the world transform of this Spatial in the most + * Computes the world transform of this Spatial in the most * efficient manner possible. */ void checkDoTransformUpdate() { @@ -670,7 +672,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * @param vp The ViewPort to which the Spatial is being rendered to. * * @see Spatial#addControl(com.jme3.scene.control.Control) - * @see Spatial#getControl(java.lang.Class) + * @see Spatial#getControl(java.lang.Class) */ public void runControlRender(RenderManager rm, ViewPort vp) { if (controls.isEmpty()) { @@ -686,26 +688,26 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * Add a control to the list of controls. * @param control The control to add. * - * @see Spatial#removeControl(java.lang.Class) + * @see Spatial#removeControl(java.lang.Class) */ public void addControl(Control control) { boolean before = requiresUpdates(); controls.add(control); control.setSpatial(this); boolean after = requiresUpdates(); - + // If the requirement to be updated has changed // then we need to let the parent node know so it // can rebuild its update list. if( parent != null && before != after ) { - parent.invalidateUpdateList(); + parent.invalidateUpdateList(); } } /** * Removes the first control that is an instance of the given class. * - * @see Spatial#addControl(com.jme3.scene.control.Control) + * @see Spatial#addControl(com.jme3.scene.control.Control) */ public void removeControl(Class controlType) { boolean before = requiresUpdates(); @@ -717,23 +719,23 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab } } boolean after = requiresUpdates(); - + // If the requirement to be updated has changed // then we need to let the parent node know so it // can rebuild its update list. if( parent != null && before != after ) { - parent.invalidateUpdateList(); + parent.invalidateUpdateList(); } } /** * Removes the given control from this spatial's controls. - * + * * @param control The control to remove * @return True if the control was successfully removed. False if the * control is not assigned to this spatial. * - * @see Spatial#addControl(com.jme3.scene.control.Control) + * @see Spatial#addControl(com.jme3.scene.control.Control) */ public boolean removeControl(Control control) { boolean before = requiresUpdates(); @@ -743,14 +745,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab } boolean after = requiresUpdates(); - + // If the requirement to be updated has changed // then we need to let the parent node know so it // can rebuild its update list. if( parent != null && before != after ) { - parent.invalidateUpdateList(); + parent.invalidateUpdateList(); } - + return result; } @@ -761,7 +763,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * @param controlType The superclass of the control to look for. * @return The first instance in the list of the controlType class, or null. * - * @see Spatial#addControl(com.jme3.scene.control.Control) + * @see Spatial#addControl(com.jme3.scene.control.Control) */ public T getControl(Class controlType) { for (Control c : controls.getArray()) { @@ -790,7 +792,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab /** * @return The number of controls attached to this Spatial. * @see Spatial#addControl(com.jme3.scene.control.Control) - * @see Spatial#removeControl(java.lang.Class) + * @see Spatial#removeControl(java.lang.Class) */ public int getNumControls() { return controls.size(); @@ -815,7 +817,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * Calling this when the Spatial is attached to a node * will cause undefined results. User code should only call this * method on Spatials having no parent. - * + * * @see Spatial#getWorldLightList() * @see Spatial#getWorldTransform() * @see Spatial#getWorldBound() @@ -835,7 +837,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab if ((refreshFlags & RF_BOUND) != 0) { updateWorldBound(); } - + assert refreshFlags == 0; } @@ -1067,9 +1069,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab /** * removeLight removes the given light from the Spatial. - * + * * @param light The light to remove. - * @see Spatial#addLight(com.jme3.light.Light) + * @see Spatial#addLight(com.jme3.light.Light) */ public void removeLight(Light light) { localLights.remove(light); @@ -1264,7 +1266,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * All controls will be cloned using the Control.cloneForSpatial method * on the clone. * - * @see Mesh#cloneForAnim() + * @see Mesh#cloneForAnim() */ public Spatial clone(boolean cloneMaterial) { try { @@ -1328,7 +1330,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * All controls will be cloned using the Control.cloneForSpatial method * on the clone. * - * @see Mesh#cloneForAnim() + * @see Mesh#cloneForAnim() */ @Override public Spatial clone() { @@ -1344,13 +1346,59 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ public abstract Spatial deepClone(); + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public Spatial jmeClone() { + try { + Spatial clone = (Spatial)super.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + + // Clone all of the fields that need fix-ups and/or potential + // sharing. + this.parent = cloner.clone(parent); + this.worldBound = cloner.clone(worldBound); + this.worldLights = cloner.clone(worldLights); + this.localLights = cloner.clone(localLights); + this.worldTransform = cloner.clone(worldTransform); + this.localTransform = cloner.clone(localTransform); + this.controls = cloner.clone(controls); + + // Cloner doesn't handle maps on its own just yet. + // Note: this is more advanced cloning than the old clone() method + // did because it just shallow cloned the map. In this case, we want + // to avoid all of the nasty cloneForSpatial() fixup style code that + // used to inject stuff into the clone's user data. By using cloner + // to clone the user data we get this automatically. + userData = (HashMap)userData.clone(); + for( Map.Entry e : userData.entrySet() ) { + Savable value = e.getValue(); + if( value instanceof Cloneable ) { + // Note: all JmeCloneable objects are also Cloneable so this + // catches both cases. + e.setValue(cloner.clone(value)); + } + } + } + public void setUserData(String key, Object data) { if (userData == null) { userData = new HashMap(); } if(data == null){ - userData.remove(key); + userData.remove(key); }else if (data instanceof Savable) { userData.put(key, (Savable) data); } else { @@ -1445,7 +1493,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split //the AnimControl creates the SkeletonControl for old files and add it to the spatial. //The SkeletonControl must be the last in the stack so we add the list of all other control before it. - //When backward compatibility won't be needed anymore this can be replaced by : + //When backward compatibility won't be needed anymore this can be replaced by : //controls = ic.readSavableArrayList("controlsList", null)); controls.addAll(0, ic.readSavableArrayList("controlsList", null)); @@ -1508,9 +1556,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab /** * setQueueBucket determines at what phase of the * rendering process this Spatial will rendered. See the - * {@link Bucket} enum for an explanation of the various + * {@link Bucket} enum for an explanation of the various * render queue buckets. - * + * * @param queueBucket * The bucket to use for this Spatial. */ @@ -1595,7 +1643,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * * @return store if not null, otherwise, a new matrix containing the result. * - * @see Spatial#getWorldTransform() + * @see Spatial#getWorldTransform() */ public Matrix4f getLocalToWorldMatrix(Matrix4f store) { if (store == null) { diff --git a/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java b/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java index 43cae6db5..05da60f0f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java @@ -38,6 +38,8 @@ import com.jme3.export.OutputCapsule; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** @@ -45,7 +47,7 @@ import java.io.IOException; * * @author Kirill Vainer */ -public abstract class AbstractControl implements Control { +public abstract class AbstractControl implements Control, JmeCloneable { protected boolean enabled = true; protected Spatial spatial; @@ -105,6 +107,20 @@ public abstract class AbstractControl implements Control { } } + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException( "Can't clone control for spatial", e ); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.spatial = cloner.clone(spatial); + } + public void update(float tpf) { if (!enabled) return; diff --git a/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java b/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java index 7f54f901b..96d7bdf75 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java @@ -86,12 +86,13 @@ public class BillboardControl extends AbstractControl { alignment = Alignment.Screen; } - public Control cloneForSpatial(Spatial spatial) { - BillboardControl control = new BillboardControl(); - control.alignment = this.alignment; - control.setSpatial(spatial); - return control; - } + // default implementation from AbstractControl is equivalent + //public Control cloneForSpatial(Spatial spatial) { + // BillboardControl control = new BillboardControl(); + // control.alignment = this.alignment; + // control.setSpatial(spatial); + // return control; + //} @Override protected void controlUpdate(float tpf) { diff --git a/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java b/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java index a154cbc24..4eccdfa69 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java @@ -136,13 +136,14 @@ public class CameraControl extends AbstractControl { // nothing to do } - @Override - public Control cloneForSpatial(Spatial newSpatial) { - CameraControl control = new CameraControl(camera, controlDir); - control.setSpatial(newSpatial); - control.setEnabled(isEnabled()); - return control; - } + // default implementation from AbstractControl is equivalent + //@Override + //public Control cloneForSpatial(Spatial newSpatial) { + // CameraControl control = new CameraControl(camera, controlDir); + // control.setSpatial(newSpatial); + // control.setEnabled(isEnabled()); + // return control; + //} private static final String CONTROL_DIR_NAME = "controlDir"; private static final String CAMERA_NAME = "camera"; diff --git a/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java b/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java index 029cc1b9a..36d29c542 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java @@ -167,13 +167,14 @@ public class LightControl extends AbstractControl { // nothing to do } - @Override - public Control cloneForSpatial(Spatial newSpatial) { - LightControl control = new LightControl(light, controlDir); - control.setSpatial(newSpatial); - control.setEnabled(isEnabled()); - return control; - } + // default implementation from AbstractControl is equivalent + //@Override + //public Control cloneForSpatial(Spatial newSpatial) { + // LightControl control = new LightControl(light, controlDir); + // control.setSpatial(newSpatial); + // control.setEnabled(isEnabled()); + // return control; + //} private static final String CONTROL_DIR_NAME = "controlDir"; private static final String LIGHT_NAME = "light"; diff --git a/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java b/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java index 030ccbb3a..f6b657842 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java @@ -43,6 +43,8 @@ import com.jme3.renderer.ViewPort; import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.Spatial; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** @@ -56,7 +58,7 @@ import java.io.IOException; * and will update the spatial's LOD if the camera has moved by a specified * amount. */ -public class LodControl extends AbstractControl implements Cloneable { +public class LodControl extends AbstractControl implements Cloneable, JmeCloneable { private float trisPerPixel = 1f; private float distTolerance = 1f; @@ -140,7 +142,16 @@ public class LodControl extends AbstractControl implements Cloneable { clone.lastLevel = 0; clone.numTris = numTris != null ? numTris.clone() : null; return clone; - } + } + + @Override + public Object jmeClone() { + LodControl clone = (LodControl)super.jmeClone(); + clone.lastDistance = 0; + clone.lastLevel = 0; + clone.numTris = numTris != null ? numTris.clone() : null; + return clone; + } @Override protected void controlUpdate(float tpf) { diff --git a/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java b/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java index a52bfb6ee..9c101c73e 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java @@ -35,6 +35,8 @@ import com.jme3.app.AppTask; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Future; @@ -85,6 +87,7 @@ public class UpdateControl extends AbstractControl { } + @Override public Control cloneForSpatial(Spatial newSpatial) { UpdateControl control = new UpdateControl(); control.setSpatial(newSpatial); @@ -93,4 +96,15 @@ public class UpdateControl extends AbstractControl { return control; } + @Override + public Object jmeClone() { + UpdateControl clone = (UpdateControl)super.jmeClone(); + + // This is kind of questionable since the tasks aren't cloned and have + // no reference to the new spatial or anything. They'll get run again + // but it's not clear to me why that would be desired. I'm doing it + // because the old cloneForSpatial() code does. FIXME? -pspeed + clone.taskQueue.addAll(taskQueue); + return clone; + } } diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java index 7f0bb601b..b57345664 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedGeometry.java @@ -47,19 +47,20 @@ import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.util.BufferUtils; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; import java.io.IOException; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.Arrays; public class InstancedGeometry extends Geometry { - + private static final int INSTANCE_SIZE = 16; - + private VertexBuffer[] globalInstanceData; private VertexBuffer transformInstanceData; private Geometry[] geometries = new Geometry[1]; - + private int firstUnusedIndex = 0; /** @@ -71,12 +72,12 @@ public class InstancedGeometry extends Geometry { setBatchHint(BatchHint.Never); setMaxNumInstances(1); } - + /** * Creates instanced geometry with the specified mode and name. - * - * @param name The name of the spatial. - * + * + * @param name The name of the spatial. + * * @see Spatial#Spatial(java.lang.String) */ public InstancedGeometry(String name) { @@ -85,57 +86,57 @@ public class InstancedGeometry extends Geometry { setBatchHint(BatchHint.Never); setMaxNumInstances(1); } - + /** - * Global user specified per-instance data. - * + * Global user specified per-instance data. + * * By default set to null, specify an array of VertexBuffers * via {@link #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) }. - * - * @return global user specified per-instance data. - * @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) + * + * @return global user specified per-instance data. + * @see #setGlobalUserInstanceData(com.jme3.scene.VertexBuffer[]) */ public VertexBuffer[] getGlobalUserInstanceData() { return globalInstanceData; } - + /** * Specify global user per-instance data. - * + * * By default set to null, specify an array of VertexBuffers * that contain per-instance vertex attributes. - * + * * @param globalInstanceData global user per-instance data. - * - * @throws IllegalArgumentException If one of the VertexBuffers is not + * + * @throws IllegalArgumentException If one of the VertexBuffers is not * {@link VertexBuffer#setInstanced(boolean) instanced}. */ public void setGlobalUserInstanceData(VertexBuffer[] globalInstanceData) { this.globalInstanceData = globalInstanceData; } - + /** * Specify camera specific user per-instance data. - * + * * @param transformInstanceData The transforms for each instance. */ public void setTransformUserInstanceData(VertexBuffer transformInstanceData) { this.transformInstanceData = transformInstanceData; } - + /** * Return user per-instance transform data. - * + * * @return The per-instance transform data. * - * @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer) + * @see #setTransformUserInstanceData(com.jme3.scene.VertexBuffer) */ public VertexBuffer getTransformUserInstanceData() { return transformInstanceData; } - - private void updateInstance(Matrix4f worldMatrix, float[] store, - int offset, Matrix3f tempMat3, + + private void updateInstance(Matrix4f worldMatrix, float[] store, + int offset, Matrix3f tempMat3, Quaternion tempQuat) { worldMatrix.toRotationMatrix(tempMat3); tempMat3.invertLocal(); @@ -164,17 +165,17 @@ public class InstancedGeometry extends Geometry { store[offset + 14] = worldMatrix.m23; store[offset + 15] = tempQuat.getW(); } - + /** * Set the maximum amount of instances that can be rendered by this * instanced geometry when mode is set to auto. - * + * * This re-allocates internal structures and therefore should be called - * only when necessary. - * + * only when necessary. + * * @param maxNumInstances The maximum number of instances that can be * rendered. - * + * * @throws IllegalStateException If mode is set to manual. * @throws IllegalArgumentException If maxNumInstances is zero or negative */ @@ -182,14 +183,14 @@ public class InstancedGeometry extends Geometry { if (maxNumInstances < 1) { throw new IllegalArgumentException("maxNumInstances must be 1 or higher"); } - + Geometry[] originalGeometries = geometries; this.geometries = new Geometry[maxNumInstances]; - + if (originalGeometries != null) { System.arraycopy(originalGeometries, 0, geometries, 0, originalGeometries.length); } - + // Resize instance data. if (transformInstanceData != null) { BufferUtils.destroyDirectBuffer(transformInstanceData.getData()); @@ -203,7 +204,7 @@ public class InstancedGeometry extends Geometry { BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE)); } } - + public int getMaxNumInstances() { return geometries.length; } @@ -211,12 +212,12 @@ public class InstancedGeometry extends Geometry { public int getActualNumInstances() { return firstUnusedIndex; } - + private void swap(int idx1, int idx2) { Geometry g = geometries[idx1]; geometries[idx1] = geometries[idx2]; geometries[idx2] = g; - + if (geometries[idx1] != null) { InstancedNode.setGeometryStartIndex2(geometries[idx1], idx1); } @@ -224,7 +225,7 @@ public class InstancedGeometry extends Geometry { InstancedNode.setGeometryStartIndex2(geometries[idx2], idx2); } } - + private void sanitize(boolean insideEntriesNonNull) { if (firstUnusedIndex >= geometries.length) { throw new AssertionError(); @@ -234,7 +235,7 @@ public class InstancedGeometry extends Geometry { if (geometries[i] == null) { if (insideEntriesNonNull) { throw new AssertionError(); - } + } } else if (InstancedNode.getGeometryStartIndex2(geometries[i]) != i) { throw new AssertionError(); } @@ -245,55 +246,55 @@ public class InstancedGeometry extends Geometry { } } } - + public void updateInstances() { FloatBuffer fb = (FloatBuffer) transformInstanceData.getData(); fb.limit(fb.capacity()); fb.position(0); - + TempVars vars = TempVars.get(); { float[] temp = vars.matrixWrite; - + for (int i = 0; i < firstUnusedIndex; i++) { Geometry geom = geometries[i]; if (geom == null) { geom = geometries[firstUnusedIndex - 1]; - + if (geom == null) { throw new AssertionError(); } - + swap(i, firstUnusedIndex - 1); - + while (geometries[firstUnusedIndex -1] == null) { firstUnusedIndex--; } } - + Matrix4f worldMatrix = geom.getWorldMatrix(); updateInstance(worldMatrix, temp, 0, vars.tempMat3, vars.quat1); fb.put(temp); } } vars.release(); - + fb.flip(); - + if (fb.limit() / INSTANCE_SIZE != firstUnusedIndex) { throw new AssertionError(); } transformInstanceData.updateData(fb); } - + public void deleteInstance(Geometry geom) { int idx = InstancedNode.getGeometryStartIndex2(geom); InstancedNode.setGeometryStartIndex2(geom, -1); - + geometries[idx] = null; - + if (idx == firstUnusedIndex - 1) { // Deleting the last element. // Move index back. @@ -309,12 +310,12 @@ public class InstancedGeometry extends Geometry { // Deleting element in the middle } } - + public void addInstance(Geometry geometry) { if (geometry == null) { throw new IllegalArgumentException("geometry cannot be null"); } - + // Take an index from the end. if (firstUnusedIndex + 1 >= geometries.length) { // No more room. @@ -323,15 +324,15 @@ public class InstancedGeometry extends Geometry { int freeIndex = firstUnusedIndex; firstUnusedIndex++; - + geometries[freeIndex] = geometry; InstancedNode.setGeometryStartIndex2(geometry, freeIndex); } - + public Geometry[] getGeometries() { return geometries; } - + public VertexBuffer[] getAllInstanceData() { ArrayList allData = new ArrayList(); if (transformInstanceData != null) { @@ -343,6 +344,16 @@ public class InstancedGeometry extends Geometry { return allData.toArray(new VertexBuffer[allData.size()]); } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.globalInstanceData = cloner.clone(globalInstanceData); + this.transformInstanceData = cloner.clone(transformInstanceData); + this.geometries = cloner.clone(geometries); + } + @Override public void write(JmeExporter exporter) throws IOException { super.write(exporter); @@ -350,7 +361,7 @@ public class InstancedGeometry extends Geometry { //capsule.write(currentNumInstances, "cur_num_instances", 1); capsule.write(geometries, "geometries", null); } - + @Override public void read(JmeImporter importer) throws IOException { super.read(importer); diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java index 61ec61956..c3cdd21e3 100644 --- a/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java @@ -44,20 +44,23 @@ import com.jme3.scene.control.Control; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.material.MatParam; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.HashMap; +import java.util.Map; public class InstancedNode extends GeometryGroupNode { - + static int getGeometryStartIndex2(Geometry geom) { return getGeometryStartIndex(geom); } - + static void setGeometryStartIndex2(Geometry geom, int startIndex) { setGeometryStartIndex(geom, startIndex); } - - private static final class InstanceTypeKey implements Cloneable { + + private static final class InstanceTypeKey implements Cloneable, JmeCloneable { Mesh mesh; Material material; @@ -68,7 +71,7 @@ public class InstancedNode extends GeometryGroupNode { this.material = material; this.lodLevel = lodLevel; } - + public InstanceTypeKey(){ } @@ -95,7 +98,7 @@ public class InstancedNode extends GeometryGroupNode { } return true; } - + @Override public InstanceTypeKey clone() { try { @@ -104,65 +107,94 @@ public class InstancedNode extends GeometryGroupNode { throw new AssertionError(); } } + + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch( CloneNotSupportedException e ) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.mesh = cloner.clone(mesh); + this.material = cloner.clone(material); + } } - - private static class InstancedNodeControl implements Control { + + private static class InstancedNodeControl implements Control, JmeCloneable { private InstancedNode node; - + public InstancedNodeControl() { } - + public InstancedNodeControl(InstancedNode node) { this.node = node; } - + @Override public Control cloneForSpatial(Spatial spatial) { - return this; + return this; // WARNING: Sets wrong control on spatial. Will be // fixed automatically by InstancedNode.clone() method. } - + + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException("Error cloning control", e); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.node = cloner.clone(node); + } + public void setSpatial(Spatial spatial){ } - + public void update(float tpf){ } - + public void render(RenderManager rm, ViewPort vp) { node.renderFromControl(); } - + public void write(JmeExporter ex) throws IOException { } public void read(JmeImporter im) throws IOException { } } - + protected InstancedNodeControl control; - - protected HashMap igByGeom + + protected HashMap igByGeom = new HashMap(); - + private InstanceTypeKey lookUp = new InstanceTypeKey(); - - private HashMap instancesMap = + + private HashMap instancesMap = new HashMap(); - + public InstancedNode() { super(); // NOTE: since we are deserializing, // the control is going to be added automatically here. } - + public InstancedNode(String name) { super(name); control = new InstancedNodeControl(this); addControl(control); } - + private void renderFromControl() { for (InstancedGeometry ig : instancesMap.values()) { ig.updateInstances(); @@ -191,7 +223,7 @@ public class InstancedNode extends GeometryGroupNode { return ig; } - + private void addToInstancedGeometry(Geometry geom) { Material material = geom.getMaterial(); MatParam param = material.getParam("UseInstancing"); @@ -200,20 +232,20 @@ public class InstancedNode extends GeometryGroupNode { + "parameter to true on the material prior " + "to adding it to InstancedNode"); } - + InstancedGeometry ig = lookUpByGeometry(geom); igByGeom.put(geom, ig); geom.associateWithGroupNode(this, 0); ig.addInstance(geom); } - + private void removeFromInstancedGeometry(Geometry geom) { InstancedGeometry ig = igByGeom.remove(geom); if (ig != null) { ig.deleteInstance(geom); } } - + private void relocateInInstancedGeometry(Geometry geom) { InstancedGeometry oldIG = igByGeom.get(geom); InstancedGeometry newIG = lookUpByGeometry(geom); @@ -226,7 +258,7 @@ public class InstancedNode extends GeometryGroupNode { igByGeom.put(geom, newIG); } } - + private void ungroupSceneGraph(Spatial s) { if (s instanceof Node) { for (Spatial sp : ((Node) s).getChildren()) { @@ -237,14 +269,14 @@ public class InstancedNode extends GeometryGroupNode { if (g.isGrouped()) { // Will invoke onGeometryUnassociated automatically. g.unassociateFromGroupNode(); - + if (InstancedNode.getGeometryStartIndex(g) != -1) { throw new AssertionError(); } } } } - + @Override public Spatial detachChildAt(int index) { Spatial s = super.detachChildAt(index); @@ -253,7 +285,7 @@ public class InstancedNode extends GeometryGroupNode { } return s; } - + private void instance(Spatial n) { if (n instanceof Geometry) { Geometry g = (Geometry) n; @@ -269,20 +301,20 @@ public class InstancedNode extends GeometryGroupNode { } } } - + public void instance() { instance(this); } - + @Override public Node clone() { return clone(true); } - + @Override public Node clone(boolean cloneMaterials) { InstancedNode clone = (InstancedNode)super.clone(cloneMaterials); - + if (instancesMap.size() > 0) { // Remove all instanced geometries from the clone for (int i = 0; i < clone.children.size(); i++) { @@ -296,7 +328,7 @@ public class InstancedNode extends GeometryGroupNode { } } } - + // remove original control from the clone clone.controls.remove(this.control); @@ -307,12 +339,33 @@ public class InstancedNode extends GeometryGroupNode { clone.lookUp = new InstanceTypeKey(); clone.igByGeom = new HashMap(); clone.instancesMap = new HashMap(); - + clone.instance(); - + return clone; } - + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.control = cloner.clone(control); + this.lookUp = cloner.clone(lookUp); + + HashMap newIgByGeom = new HashMap(); + for( Map.Entry e : igByGeom.entrySet() ) { + newIgByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue())); + } + this.igByGeom = newIgByGeom; + + HashMap newInstancesMap = new HashMap(); + for( Map.Entry e : instancesMap.entrySet() ) { + newInstancesMap.put(cloner.clone(e.getKey()), cloner.clone(e.getValue())); + } + this.instancesMap = newInstancesMap; + } + @Override public void onTransformChange(Geometry geom) { // Handled automatically diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java index 46dc8390e..f882ac580 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java +++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java @@ -37,6 +37,7 @@ import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.material.Material; +import com.jme3.material.RenderState; import com.jme3.math.Matrix4f; import com.jme3.math.Vector4f; import com.jme3.post.Filter; @@ -44,6 +45,7 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.renderer.queue.RenderQueue; import com.jme3.texture.FrameBuffer; + import java.io.IOException; /** @@ -74,6 +76,9 @@ public abstract class AbstractShadowFilter ext material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md"); this.shadowRenderer = shadowRenderer; this.shadowRenderer.setPostShadowMaterial(material); + + //this is legacy setting for shadows with backface shadows + this.shadowRenderer.setRenderBackFacesShadows(true); } @Override @@ -126,7 +131,7 @@ public abstract class AbstractShadowFilter ext /** * How far the shadows are rendered in the view * - * @see setShadowZExtend(float zFar) + * @see #setShadowZExtend(float zFar) * @return shadowZExtend */ public float getShadowZExtend() { @@ -248,6 +253,46 @@ public abstract class AbstractShadowFilter ext shadowRenderer.setEdgeFilteringMode(filterMode); } + /** + * + * !! WARNING !! this parameter is defaulted to true for the ShadowFilter. + * Setting it to true, may produce edges artifacts on shadows. * + * + * Set to true if you want back faces shadows on geometries. + * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting. + * + * Setting this parameter will override this parameter for ALL materials in the scene. + * This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass. + * You can modify them by using {@link #getPreShadowForcedRenderState()} + * + * If you want to set it differently for each material in the scene you have to use the ShadowRenderer instead + * of the shadow filter. + * + * @param renderBackFacesShadows true or false. + */ + public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) { + shadowRenderer.setRenderBackFacesShadows(renderBackFacesShadows); + } + + /** + * if this filter renders back faces shadows + * @return true if this filter renders back faces shadows + */ + public boolean isRenderBackFacesShadows() { + return shadowRenderer.isRenderBackFacesShadows(); + } + + /** + * returns the pre shadows pass render state. + * use it to adjust the RenderState parameters of the pre shadow pass. + * Note that this will be overriden if the preShadow technique in the material has a ForcedRenderState + * @return the pre shadow render state. + */ + public RenderState getPreShadowForcedRenderState() { + return shadowRenderer.getPreShadowForcedRenderState(); + } + + /** * returns the the edge filtering mode * diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java index bd70465cb..b55c6b2d2 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java @@ -31,10 +31,6 @@ */ package com.jme3.shadow; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import com.jme3.asset.AssetManager; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; @@ -42,6 +38,7 @@ import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.export.Savable; import com.jme3.material.Material; +import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; import com.jme3.math.Matrix4f; import com.jme3.math.Vector2f; @@ -67,6 +64,10 @@ import com.jme3.texture.Texture.ShadowCompareMode; import com.jme3.texture.Texture2D; import com.jme3.ui.Picture; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + /** * abstract shadow renderer that holds commons feature to have for a shadow * renderer @@ -92,6 +93,9 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable protected EdgeFilteringMode edgeFilteringMode = EdgeFilteringMode.Bilinear; protected CompareMode shadowCompareMode = CompareMode.Hardware; protected Picture[] dispPic; + protected RenderState forcedRenderState = new RenderState(); + protected Boolean renderBackFacesShadows; + /** * true if the fallback material should be used, otherwise false */ @@ -181,6 +185,14 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable setShadowCompareMode(shadowCompareMode); setEdgeFilteringMode(edgeFilteringMode); setShadowIntensity(shadowIntensity); + initForcedRenderState(); + } + + protected void initForcedRenderState() { + forcedRenderState.setFaceCullMode(RenderState.FaceCullMode.Front); + forcedRenderState.setColorWrite(false); + forcedRenderState.setDepthWrite(true); + forcedRenderState.setDepthTest(true); } /** @@ -356,9 +368,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable * rendered in the shadow map * * @param shadowMapIndex the index of the shadow map being rendered - * @param sceneOccluders the occluders of the whole scene - * @param sceneReceivers the receivers of the whole scene - * @param shadowMapOcculders + * @param shadowMapOccluders the list of occluders * @return */ protected abstract GeometryList getOccludersToRender(int shadowMapIndex, GeometryList shadowMapOccluders); @@ -425,9 +435,11 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable renderManager.getRenderer().setFrameBuffer(shadowFB[shadowMapIndex]); renderManager.getRenderer().clearBuffers(true, true, true); + renderManager.setForcedRenderState(forcedRenderState); // render shadow casters to shadow map viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true); + renderManager.setForcedRenderState(null); } boolean debugfrustums = false; @@ -535,18 +547,7 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable private void setMatParams(GeometryList l) { //iteration throught all the geometries of the list to gather the materials - matCache.clear(); - for (int i = 0; i < l.size(); i++) { - Material mat = l.get(i).getMaterial(); - //checking if the material has the post technique and adding it to the material cache - if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) { - if (!matCache.contains(mat)) { - matCache.add(mat); - } - } else { - needsfallBackMaterial = true; - } - } + buildMatCache(l); //iterating through the mat cache and setting the parameters for (Material mat : matCache) { @@ -566,6 +567,10 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable if (fadeInfo != null) { mat.setVector2("FadeInfo", fadeInfo); } + if(renderBackFacesShadows != null){ + mat.setBoolean("BackfaceShadows", renderBackFacesShadows); + } + setMaterialParameters(mat); } @@ -577,6 +582,21 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable } + private void buildMatCache(GeometryList l) { + matCache.clear(); + for (int i = 0; i < l.size(); i++) { + Material mat = l.get(i).getMaterial(); + //checking if the material has the post technique and adding it to the material cache + if (mat.getMaterialDef().getTechniqueDef(postTechniqueName) != null) { + if (!matCache.contains(mat)) { + matCache.add(mat); + } + } else { + needsfallBackMaterial = true; + } + } + } + /** * for internal use only */ @@ -587,7 +607,10 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable postshadowMat.setTexture(shadowMapStringCache[j], shadowMaps[j]); } if (fadeInfo != null) { - postshadowMat.setVector2("FadeInfo", fadeInfo); + postshadowMat.setVector2("FadeInfo", fadeInfo); + } + if(renderBackFacesShadows != null){ + postshadowMat.setBoolean("BackfaceShadows", renderBackFacesShadows); } } @@ -730,6 +753,48 @@ public abstract class AbstractShadowRenderer implements SceneProcessor, Savable @Deprecated public void setFlushQueues(boolean flushQueues) {} + + /** + * returns the pre shadows pass render state. + * use it to adjust the RenderState parameters of the pre shadow pass. + * Note that this will be overriden if the preShadow technique in the material has a ForcedRenderState + * @return the pre shadow render state. + */ + public RenderState getPreShadowForcedRenderState() { + return forcedRenderState; + } + + /** + * Set to true if you want back faces shadows on geometries. + * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting. + * + * Also note that setting this parameter will override this parameter for ALL materials in the scene. + * You can alternatively change this parameter on a single material using {@link Material#setBoolean(String, boolean)} + * + * This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass. + * You can modify them by using {@link #getPreShadowForcedRenderState()} + * + * @param renderBackFacesShadows true or false. + */ + public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) { + this.renderBackFacesShadows = renderBackFacesShadows; + if(renderBackFacesShadows) { + getPreShadowForcedRenderState().setPolyOffset(5, 3); + getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Back); + }else{ + getPreShadowForcedRenderState().setPolyOffset(0, 0); + getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Front); + } + } + + /** + * if this processor renders back faces shadows + * @return true if this processor renders back faces shadows + */ + public boolean isRenderBackFacesShadows() { + return renderBackFacesShadows != null?renderBackFacesShadows:false; + } + /** * De-serialize this instance, for example when loading from a J3O file. * diff --git a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java index acf8a9677..33adf1c09 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java @@ -215,6 +215,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer { @Override protected void setMaterialParameters(Material material) { material.setColor("Splits", splits); + material.setVector3("LightDir", light.getDirection()); if (fadeInfo != null) { material.setVector2("FadeInfo", fadeInfo); } @@ -224,6 +225,7 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer { protected void clearMaterialParameters(Material material) { material.clearParam("Splits"); material.clearParam("FadeInfo"); + material.clearParam("LightDir"); } /** diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java index 2134e0a7e..e6516df0b 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java @@ -157,6 +157,8 @@ public abstract class JmeSystemDelegate { return false; } else if (arch.equals("aarch64")) { return true; + } else if (arch.equals("armv7") || arch.equals("armv7l")) { + return false; } else if (arch.equals("arm")) { return false; } else { diff --git a/jme3-core/src/main/java/com/jme3/util/IntMap.java b/jme3-core/src/main/java/com/jme3/util/IntMap.java index 38ffb2431..b572fad22 100644 --- a/jme3-core/src/main/java/com/jme3/util/IntMap.java +++ b/jme3-core/src/main/java/com/jme3/util/IntMap.java @@ -32,19 +32,21 @@ package com.jme3.util; import com.jme3.util.IntMap.Entry; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.util.Iterator; import java.util.Map; import java.util.NoSuchElementException; /** * Similar to a {@link Map} except that ints are used as keys. - * + * * Taken from http://code.google.com/p/skorpios/ - * - * @author Nate + * + * @author Nate */ -public final class IntMap implements Iterable>, Cloneable { - +public final class IntMap implements Iterable>, Cloneable, JmeCloneable { + private Entry[] table; private final float loadFactor; private int size, mask, capacity, threshold; @@ -93,6 +95,26 @@ public final class IntMap implements Iterable>, Cloneable { return null; } + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.table = cloner.clone(table); + } + public boolean containsValue(Object value) { Entry[] table = this.table; for (int i = table.length; i-- > 0;){ @@ -228,7 +250,7 @@ public final class IntMap implements Iterable>, Cloneable { idx = 0; el = 0; } - + public boolean hasNext() { return el < size; } @@ -255,20 +277,20 @@ public final class IntMap implements Iterable>, Cloneable { // the entry was null. find another non-null entry. cur = table[++idx]; } while (cur == null); - + Entry e = cur; cur = cur.next; el ++; - + return e; } public void remove() { } - + } - - public static final class Entry implements Cloneable { + + public static final class Entry implements Cloneable, JmeCloneable { final int key; T value; @@ -303,5 +325,20 @@ public final class IntMap implements Iterable>, Cloneable { } return null; } + + @Override + public Object jmeClone() { + try { + return super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.value = cloner.clone(value); + this.next = cloner.clone(next); + } } } diff --git a/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java new file mode 100644 index 000000000..cb5873007 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2016 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.util.clone; + + +/** + * Provides custom cloning for a particular object type. Once + * registered with the Cloner, this function object will be called twice + * for any cloned object that matches the class for which it was registered. + * It will first call cloneObject() to shallow clone the object and then call + * cloneFields() to deep clone the object's values. + * + *

This two step process is important because this is what allows + * circular references in the cloned object graph.

+ * + * @author Paul Speed + */ +public interface CloneFunction { + + /** + * Performs a shallow clone of the specified object. This is similar + * to the JmeCloneable.clone() method in semantics and is the first part + * of a two part cloning process. Once the shallow clone is created, it + * is cached and CloneFunction.cloneFields() is called. In this way, + * the CloneFunction interface can completely take over the JmeCloneable + * style cloning for an object that doesn't otherwise implement that interface. + * + * @param cloner The cloner performing the cloning operation. + * @param original The original object that needs to be cloned. + */ + public T cloneObject( Cloner cloner, T original ); + + + /** + * Performs a deep clone of the specified clone's fields. This is similar + * to the JmeCloneable.cloneFields() method in semantics and is the second part + * of a two part cloning process. Once the shallow clone is created, it + * is cached and CloneFunction.cloneFields() is called. In this way, + * the CloneFunction interface can completely take over the JmeCloneable + * style cloning for an object that doesn't otherwise implement that interface. + * + * @param cloner The cloner performing the cloning operation. + * @param clone The clone previously returned from cloneObject(). + * @param original The original object that was cloned. This is provided for + * the very special case where field cloning needs to refer to + * the original object. Mostly the necessary fields should already + * be on the clone. + */ + public void cloneFields( Cloner cloner, T clone, T original ); + +} diff --git a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java new file mode 100644 index 000000000..cc046b28a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2016 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.util.clone; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A deep clone utility that provides similar object-graph-preserving + * qualities to typical serialization schemes. An internal registry + * of cloned objects is kept to be used by other objects in the deep + * clone process that implement JmeCloneable. + * + *

By default, objects that do not implement JmeCloneable will + * be treated like normal Java Cloneable objects. If the object does + * not implement the JmeCloneable or the regular JDK Cloneable interfaces + * AND has no special handling defined then an IllegalArgumentException + * will be thrown.

+ * + *

Enhanced object cloning is done in a two step process. First, + * the object is cloned using the normal Java clone() method and stored + * in the clone registry. After that, if it implements JmeCloneable then + * its cloneFields() method is called to deep clone any of the fields. + * This two step process has a few benefits. First, it means that objects + * can easily have a regular shallow clone implementation just like any + * normal Java objects. Second, the deep cloning of fields happens after + * creation wich means that the clone is available to future field cloning + * to resolve circular references.

+ * + *

Similar to Java serialization, the handling of specific object + * types can be customized. This allows certain objects to be cloned gracefully + * even if they aren't normally Cloneable. This can also be used as a + * sort of filter to keep certain types of objects from being cloned. + * (For example, adding the IdentityCloneFunction for Mesh.class would cause + * all mesh instances to be shared with the original object graph.)

+ * + *

By default, the Cloner registers serveral default clone functions + * as follows:

+ *
    + *
  • java.util.ArrayList: ListCloneFunction + *
  • java.util.LinkedList: ListCloneFunction + *
  • java.util.concurrent.CopyOnWriteArrayList: ListCloneFunction + *
  • java.util.Vector: ListCloneFunction + *
  • java.util.Stack: ListCloneFunction + *
  • com.jme3.util.SafeArrayList: ListCloneFunction + *
+ * + *

Usage:

+ *
+ *  // Example 1: using an instantiated, reusable cloner.
+ *  Cloner cloner = new Cloner();
+ *  Foo fooClone = cloner.clone(foo);
+ *  cloner.clearIndex(); // prepare it for reuse
+ *  Foo fooClone2 = cloner.clone(foo);
+ * 
+ *  // Example 2: using the utility method that self-instantiates a temporary cloner.
+ *  Foo fooClone = Cloner.deepClone(foo);
+ *   
+ *  
+ * + * @author Paul Speed + */ +public class Cloner { + + /** + * Keeps track of the objects that have been cloned so far. + */ + private IdentityHashMap index = new IdentityHashMap(); + + /** + * Custom functions for cloning objects. + */ + private Map functions = new HashMap(); + + /** + * Cache the clone methods once for all cloners. + */ + private static final Map methodCache = new ConcurrentHashMap<>(); + + /** + * Creates a new cloner with only default clone functions and an empty + * object index. + */ + public Cloner() { + // Register some standard types + ListCloneFunction listFunction = new ListCloneFunction(); + functions.put(java.util.ArrayList.class, listFunction); + functions.put(java.util.LinkedList.class, listFunction); + functions.put(java.util.concurrent.CopyOnWriteArrayList.class, listFunction); + functions.put(java.util.Vector.class, listFunction); + functions.put(java.util.Stack.class, listFunction); + functions.put(com.jme3.util.SafeArrayList.class, listFunction); + } + + /** + * Convenience utility function that creates a new Cloner, uses it to + * deep clone the object, and then returns the result. + */ + public static T deepClone( T object ) { + return new Cloner().clone(object); + } + + /** + * Deeps clones the specified object, reusing previous clones when possible. + * + *

Object cloning priority works as follows:

+ *
    + *
  • If the object has already been cloned then its clone is returned. + *
  • If there is a custom CloneFunction then it is called to clone the object. + *
  • If the object implements Cloneable then its clone() method is called, arrays are + * deep cloned with entries passing through clone(). + *
  • If the object implements JmeCloneable then its cloneFields() method is called on the + * clone. + *
  • Else an IllegalArgumentException is thrown. + *
+ * + * Note: objects returned by this method may not have yet had their cloneField() + * method called. + */ + public T clone( T object ) { + return clone(object, true); + } + + /** + * Internal method to work around a Java generics typing issue by + * isolating the 'bad' case into a method with suppressed warnings. + */ + @SuppressWarnings("unchecked") + private Class objectClass( T object ) { + // This should be 100% allowed without a cast but Java generics + // is not that smart sometimes. + // Wrapping it in a method at least isolates the warning suppression + return (Class)object.getClass(); + } + + /** + * Deeps clones the specified object, reusing previous clones when possible. + * + *

Object cloning priority works as follows:

+ *
    + *
  • If the object has already been cloned then its clone is returned. + *
  • If useFunctions is true and there is a custom CloneFunction then it is + * called to clone the object. + *
  • If the object implements Cloneable then its clone() method is called, arrays are + * deep cloned with entries passing through clone(). + *
  • If the object implements JmeCloneable then its cloneFields() method is called on the + * clone. + *
  • Else an IllegalArgumentException is thrown. + *
+ * + *

The abililty to selectively use clone functions is useful when + * being called from a clone function.

+ * + * Note: objects returned by this method may not have yet had their cloneField() + * method called. + */ + public T clone( T object, boolean useFunctions ) { + if( object == null ) { + return null; + } + Class type = objectClass(object); + + // Check the index to see if we already have it + Object clone = index.get(object); + if( clone != null ) { + return type.cast(clone); + } + + // See if there is a custom function... that trumps everything. + CloneFunction f = getCloneFunction(type); + if( f != null ) { + T result = f.cloneObject(this, object); + + // Store the object in the identity map so that any circular references + // are resolvable. + index.put(object, result); + + // Now call the function again to deep clone the fields + f.cloneFields(this, result, object); + + return result; + } + + if( object.getClass().isArray() ) { + // Perform an array clone + clone = arrayClone(object); + + // Array clone already indexes the clone + } else if( object instanceof JmeCloneable ) { + // Use the two-step cloning semantics + clone = ((JmeCloneable)object).jmeClone(); + + // Store the object in the identity map so that any circular references + // are resolvable + index.put(object, clone); + + ((JmeCloneable)clone).cloneFields(this, object); + } else if( object instanceof Cloneable ) { + + // Perform a regular Java shallow clone + try { + clone = javaClone(object); + } catch( CloneNotSupportedException e ) { + throw new IllegalArgumentException("Object is not cloneable, type:" + type, e); + } + + // Store the object in the identity map so that any circular references + // are resolvable + index.put(object, clone); + } else { + throw new IllegalArgumentException("Object is not cloneable, type:" + type); + } + + return type.cast(clone); + } + + /** + * Sets a custom CloneFunction for the exact Java type. Note: no inheritence + * checks are performed so a function must be registered for each specific type + * that it handles. By default ListCloneFunction is registered for + * ArrayList, LinkedList, CopyOnWriteArrayList, Vector, Stack, and JME's SafeArrayList. + */ + public void setCloneFunction( Class type, CloneFunction function ) { + if( function == null ) { + functions.remove(type); + } else { + functions.put(type, function); + } + } + + /** + * Returns a previously registered clone function for the specified type or null + * if there is no custom clone function for the type. + */ + @SuppressWarnings("unchecked") + public CloneFunction getCloneFunction( Class type ) { + return (CloneFunction)functions.get(type); + } + + /** + * Clears the object index allowing the cloner to be reused for a brand new + * cloning operation. + */ + public void clearIndex() { + index.clear(); + } + + /** + * Performs a raw shallow Java clone using reflection. This call does NOT + * check against the clone index and so will return new objects every time + * it is called. That's because these are shallow clones and have not (and may + * not ever, depending on the caller) get resolved. + * + *

This method is provided as a convenient way for CloneFunctions to call + * clone() and objects without necessarily knowing their real type.

+ */ + public T javaClone( T object ) throws CloneNotSupportedException { + Method m = methodCache.get(object.getClass()); + if( m == null ) { + try { + // Lookup the method and cache it + m = object.getClass().getMethod("clone"); + } catch( NoSuchMethodException e ) { + throw new CloneNotSupportedException("No public clone method found for:" + object.getClass()); + } + methodCache.put(object.getClass(), m); + + // Note: yes we might cache the method twice... but so what? + } + + try { + Class type = objectClass(object); + return type.cast(m.invoke(object)); + } catch( IllegalAccessException | InvocationTargetException e ) { + throw new RuntimeException("Error cloning object of type:" + object.getClass(), e); + } + } + + /** + * Clones a primitive array by coping it and clones an object + * array by coping it and then running each of its values through + * Cloner.clone(). + */ + protected T arrayClone( T object ) { + + // Java doesn't support the cloning of arrays through reflection unless + // you open access to Object's protected clone array... which requires + // elevated privileges. So we will do a work-around that is slightly less + // elegant. + // This should be 100% allowed without a case but Java generics + // is not that smart + Class type = objectClass(object); + Class elementType = type.getComponentType(); + int size = Array.getLength(object); + Object clone = Array.newInstance(elementType, size); + + // Store the clone for later lookups + index.put(object, clone); + + if( elementType.isPrimitive() ) { + // Then our job is a bit easier + System.arraycopy(object, 0, clone, 0, size); + } else { + // Else it's an object array so we'll clone it and its children + for( int i = 0; i < size; i++ ) { + Object element = clone(Array.get(object, i)); + Array.set(clone, i, element); + } + } + + return type.cast(clone); + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java new file mode 100644 index 000000000..ac3dce6ee --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.util.clone; + + +/** + * A CloneFunction implementation that simply returns the + * the passed object without cloning it. This is useful for + * forcing some object types (like Meshes) to be shared between + * the original and cloned object graph. + * + * @author Paul Speed + */ +public class IdentityCloneFunction implements CloneFunction { + + /** + * Returns the object directly. + */ + public T cloneObject( Cloner cloner, T object ) { + return object; + } + + /** + * Does nothing. + */ + public void cloneFields( Cloner cloner, T clone, T object ) { + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java b/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java new file mode 100644 index 000000000..6b278b222 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2016 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.util.clone; + + +/** + * Indicates an object that wishes to more actively participate in the + * two-part deep copying process provided by the Cloner. Objects implementing + * this interface can access the already cloned object graph to resolve + * their local dependencies in a way that will be equivalent to the + * original object graph. In other words, if two objects in the graph + * share the same target reference then the cloned version will share + * the cloned reference. + * + *

For example, if an object wishes to deep clone one of its fields + * then it will call cloner.clone(object) instead of object.clone(). + * The cloner will keep track of any clones already created for 'object' + * and return that instead of a new clone.

+ * + *

Cloning of a JmeCloneable object is done in two parts. First, + * the standard Java clone() method is called to create a shallow clone + * of the object. Second, the cloner wil lcall the cloneFields() method + * to let the object deep clone any of its fields that should be cloned.

+ * + *

This two part process is necessary to facilitate circular references. + * When an object calls cloner.clone() during its cloneFields() method, it + * may get only a shallow copy that will be filled in later.

+ * + * @author Paul Speed + */ +public interface JmeCloneable extends Cloneable { + + /** + * Performs a regular shallow clone of the object. Some fields + * may also be cloned but generally only if they will never be + * shared with other objects. (For example, local Vector3fs and so on.) + * + *

This method is separate from the regular clone() method + * so that objects might still maintain their own regular java clone() + * semantics (perhaps even using Cloner for those methods). However, + * because Java's clone() has specific features in the sense of Object's + * clone() implementation, it's usually best to have some path for + * subclasses to bypass the public clone() method that might be cloning + * fields and instead get at the superclass protected clone() methods. + * For example, through super.jmeClone() or another protected clone + * method that some base class eventually calls super.clone() in.

+ */ + public Object jmeClone(); + + /** + * Implemented to perform deep cloning for this object, resolving + * local cloned references using the specified cloner. The object + * can call cloner.clone(fieldValue) to deep clone any of its fields. + * + *

Note: during normal clone operations the original object + * will not be needed as the clone has already had all of the fields + * shallow copied.

+ * + * @param cloner The cloner that is performing the cloning operation. The + * cloneFields method can call back into the cloner to make + * clones if its subordinate fields. + * @param original The original object from which this object was cloned. + * This is provided for the very rare case that this object needs + * to refer to its original for some reason. In general, all of + * the relevant values should have been transferred during the + * shallow clone and this object need merely clone what it wants. + */ + public void cloneFields( Cloner cloner, Object original ); +} diff --git a/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java new file mode 100644 index 000000000..a1f269d67 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.util.clone; + +import java.util.List; + +/** + * A CloneFunction implementation that deep clones a list by + * creating a new list and cloning its values using the cloner. + * + * @author Paul Speed + */ +public class ListCloneFunction implements CloneFunction { + + public T cloneObject( Cloner cloner, T object ) { + try { + T clone = cloner.javaClone(object); + return clone; + } catch( CloneNotSupportedException e ) { + throw new IllegalArgumentException("Clone not supported for type:" + object.getClass(), e); + } + } + + /** + * Clones the elements of the list. + */ + @SuppressWarnings("unchecked") + public void cloneFields( Cloner cloner, T clone, T object ) { + for( int i = 0; i < clone.size(); i++ ) { + // Need to clone the clones... because T might + // have done something special in its clone method that + // we will have to adhere to. For example, clone may have nulled + // out some things or whatever that might be implementation specific. + // At any rate, if it's a proper clone then the clone will already + // have shallow versions of the elements that we can clone. + clone.set(i, cloner.clone(clone.get(i))); + } + } +} + diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md index 2a4d934d2..4c27dd90e 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md @@ -113,6 +113,8 @@ MaterialDef Phong Lighting { //For instancing Boolean UseInstancing + + Boolean BackfaceShadows: false } Technique { @@ -214,26 +216,19 @@ MaterialDef Phong Lighting { INSTANCING : UseInstancing } - ForcedRenderState { - FaceCull Off - DepthTest On - DepthWrite On - PolyOffset 5 3 - ColorWrite Off - } - } Technique PostShadow15{ - VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert - FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag + VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix WorldMatrix ViewProjectionMatrix ViewMatrix + NormalMatrix } Defines { @@ -248,6 +243,7 @@ MaterialDef Phong Lighting { POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows } ForcedRenderState { @@ -266,6 +262,7 @@ MaterialDef Phong Lighting { WorldMatrix ViewProjectionMatrix ViewMatrix + NormalMatrix } Defines { diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag index 254806d87..3ece6268f 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag @@ -41,8 +41,7 @@ varying vec3 SpecularSum; #ifdef NORMALMAP uniform sampler2D m_NormalMap; - varying vec3 vTangent; - varying vec3 vBinormal; + varying vec4 vTangent; #endif varying vec3 vNormal; @@ -71,7 +70,7 @@ uniform float m_Shininess; void main(){ #if !defined(VERTEX_LIGHTING) #if defined(NORMALMAP) - mat3 tbnMat = mat3(normalize(vTangent.xyz) , normalize(vBinormal.xyz) , normalize(vNormal.xyz)); + mat3 tbnMat = mat3(vTangent.xyz, vTangent.w * cross( (vNormal), (vTangent.xyz)), vNormal.xyz); if (!gl_FrontFacing) { diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert index 1fde8e13d..c607b3891 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert @@ -39,8 +39,7 @@ attribute vec3 inNormal; varying vec3 vPos; #ifdef NORMALMAP attribute vec4 inTangent; - varying vec3 vTangent; - varying vec3 vBinormal; + varying vec4 vTangent; #endif #else #ifdef COLORRAMP @@ -104,8 +103,7 @@ void main(){ #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) - vTangent = TransformNormal(modelSpaceTan); - vBinormal = cross(wvNormal, vTangent)* inTangent.w; + vTangent = vec4(TransformNormal(modelSpaceTan).xyz,inTangent.w); vNormal = wvNormal; vPos = wvPosition; #elif !defined(VERTEX_LIGHTING) diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md index 8dd6fc5ca..68f07f98d 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md @@ -51,6 +51,8 @@ MaterialDef Unshaded { Float PCFEdge Float ShadowMapSize + + Boolean BackfaceShadows: true } Technique { @@ -147,8 +149,8 @@ MaterialDef Unshaded { Technique PostShadow15{ - VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert - FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag + VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -169,6 +171,7 @@ MaterialDef Unshaded { POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows } ForcedRenderState { @@ -201,6 +204,7 @@ MaterialDef Unshaded { POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows } ForcedRenderState { diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag index 4e42c5784..a2d191895 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.frag @@ -1,4 +1,5 @@ #import "Common/ShaderLib/Shadows.glsllib" +#import "Common/ShaderLib/GLSLCompat.glsllib" #if defined(PSSM) || defined(FADE) varying float shadowPosition; @@ -8,6 +9,9 @@ varying vec4 projCoord0; varying vec4 projCoord1; varying vec4 projCoord2; varying vec4 projCoord3; +#ifndef BACKFACE_SHADOWS + varying float nDotL; +#endif #ifdef POINTLIGHT varying vec4 projCoord4; @@ -45,9 +49,15 @@ void main(){ if(alpha<=m_AlphaDiscardThreshold){ discard; } + #endif + #ifndef BACKFACE_SHADOWS + if(nDotL > 0.0){ + discard; + } #endif - + + float shadow = 1.0; #ifdef POINTLIGHT @@ -70,11 +80,11 @@ void main(){ #endif #ifdef FADE - shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y)); + shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y)); #endif - shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); - gl_FragColor = vec4(shadow, shadow, shadow, 1.0); + shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); + gl_FragColor = vec4(shadow, shadow, shadow, 1.0); } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md index af80bdae3..928637adf 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md @@ -29,12 +29,14 @@ MaterialDef Post Shadow { Float PCFEdge Float ShadowMapSize + + Boolean BackfaceShadows: false } Technique { - VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow15.vert - FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag + VertexShader GLSL150: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -49,6 +51,7 @@ MaterialDef Post Shadow { FADE : FadeInfo PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 + BACKFACE_SHADOWS: BackfaceShadows } RenderState { @@ -75,6 +78,7 @@ MaterialDef Post Shadow { FADE : FadeInfo PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 + BACKFACE_SHADOWS: BackfaceShadows } RenderState { diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert index 482231032..de4490820 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert @@ -1,11 +1,12 @@ #import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Skinning.glsllib" +#import "Common/ShaderLib/GLSLCompat.glsllib" + uniform mat4 m_LightViewProjectionMatrix0; uniform mat4 m_LightViewProjectionMatrix1; uniform mat4 m_LightViewProjectionMatrix2; uniform mat4 m_LightViewProjectionMatrix3; -uniform vec3 m_LightPos; varying vec4 projCoord0; varying vec4 projCoord1; @@ -15,12 +16,14 @@ varying vec4 projCoord3; #ifdef POINTLIGHT uniform mat4 m_LightViewProjectionMatrix4; uniform mat4 m_LightViewProjectionMatrix5; + uniform vec3 m_LightPos; varying vec4 projCoord4; varying vec4 projCoord5; varying vec4 worldPos; #else + uniform vec3 m_LightDir; #ifndef PSSM - uniform vec3 m_LightDir; + uniform vec3 m_LightPos; varying float lightDot; #endif #endif @@ -28,12 +31,15 @@ varying vec4 projCoord3; #if defined(PSSM) || defined(FADE) varying float shadowPosition; #endif -varying vec3 lightVec; varying vec2 texCoord; - attribute vec3 inPosition; +#ifndef BACKFACE_SHADOWS + attribute vec3 inNormal; + varying float nDotL; +#endif + #ifdef DISCARD_ALPHA attribute vec2 inTexCoord; #endif @@ -51,16 +57,17 @@ void main(){ Skinning_Compute(modelSpacePos); #endif gl_Position = TransformWorldViewProjection(modelSpacePos); + vec3 lightDir; #if defined(PSSM) || defined(FADE) - shadowPosition = gl_Position.z; + shadowPosition = gl_Position.z; #endif #ifndef POINTLIGHT vec4 worldPos=vec4(0.0); #endif // get the vertex in world space - worldPos = g_WorldMatrix * modelSpacePos; + worldPos = TransformWorld(modelSpacePos); #ifdef DISCARD_ALPHA texCoord = inTexCoord; @@ -75,8 +82,21 @@ void main(){ projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos; #else #ifndef PSSM - vec3 lightDir = worldPos.xyz - m_LightPos; + //Spot light + lightDir = worldPos.xyz - m_LightPos; lightDot = dot(m_LightDir,lightDir); #endif #endif + + #ifndef BACKFACE_SHADOWS + vec3 normal = normalize(TransformWorld(vec4(inNormal,0.0))).xyz; + #ifdef POINTLIGHT + lightDir = worldPos.xyz - m_LightPos; + #else + #ifdef PSSM + lightDir = m_LightDir; + #endif + #endif + nDotL = dot(normal, lightDir); + #endif } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag deleted file mode 100644 index 2eb9541e5..000000000 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.frag +++ /dev/null @@ -1,80 +0,0 @@ -#import "Common/ShaderLib/Shadows15.glsllib" - -out vec4 outFragColor; - -#if defined(PSSM) || defined(FADE) -in float shadowPosition; -#endif - -in vec4 projCoord0; -in vec4 projCoord1; -in vec4 projCoord2; -in vec4 projCoord3; - -#ifdef POINTLIGHT - in vec4 projCoord4; - in vec4 projCoord5; - in vec4 worldPos; - uniform vec3 m_LightPos; -#else - #ifndef PSSM - in float lightDot; - #endif -#endif - -#ifdef DISCARD_ALPHA - #ifdef COLOR_MAP - uniform sampler2D m_ColorMap; - #else - uniform sampler2D m_DiffuseMap; - #endif - uniform float m_AlphaDiscardThreshold; - varying vec2 texCoord; -#endif - -#ifdef FADE -uniform vec2 m_FadeInfo; -#endif - -void main(){ - - #ifdef DISCARD_ALPHA - #ifdef COLOR_MAP - float alpha = texture2D(m_ColorMap,texCoord).a; - #else - float alpha = texture2D(m_DiffuseMap,texCoord).a; - #endif - - if(alpha < m_AlphaDiscardThreshold){ - discard; - } - #endif - - float shadow = 1.0; - #ifdef POINTLIGHT - shadow = getPointLightShadows(worldPos, m_LightPos, - m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5, - projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5); - #else - #ifdef PSSM - shadow = getDirectionalLightShadows(m_Splits, shadowPosition, - m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3, - projCoord0, projCoord1, projCoord2, projCoord3); - #else - //spotlight - if(lightDot < 0){ - outFragColor = vec4(1.0); - return; - } - shadow = getSpotLightShadows(m_ShadowMap0,projCoord0); - #endif - #endif - - #ifdef FADE - shadow = max(0.0,mix(shadow,1.0,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y)); - #endif - - shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); - outFragColor = vec4(shadow, shadow, shadow, 1.0); -} - diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert deleted file mode 100644 index 20565de7c..000000000 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow15.vert +++ /dev/null @@ -1,82 +0,0 @@ -#import "Common/ShaderLib/Instancing.glsllib" -#import "Common/ShaderLib/Skinning.glsllib" -uniform mat4 m_LightViewProjectionMatrix0; -uniform mat4 m_LightViewProjectionMatrix1; -uniform mat4 m_LightViewProjectionMatrix2; -uniform mat4 m_LightViewProjectionMatrix3; - - -out vec4 projCoord0; -out vec4 projCoord1; -out vec4 projCoord2; -out vec4 projCoord3; - -#ifdef POINTLIGHT - uniform mat4 m_LightViewProjectionMatrix4; - uniform mat4 m_LightViewProjectionMatrix5; - out vec4 projCoord4; - out vec4 projCoord5; - out vec4 worldPos; -#else - #ifndef PSSM - uniform vec3 m_LightPos; - uniform vec3 m_LightDir; - out float lightDot; - #endif -#endif - -#if defined(PSSM) || defined(FADE) - out float shadowPosition; -#endif -out vec3 lightVec; - -out vec2 texCoord; - -in vec3 inPosition; - -#ifdef DISCARD_ALPHA - in vec2 inTexCoord; -#endif - -const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0, - 0.0, 0.5, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.5, 0.5, 0.5, 1.0); - - -void main(){ - vec4 modelSpacePos = vec4(inPosition, 1.0); - - #ifdef NUM_BONES - Skinning_Compute(modelSpacePos); - #endif - gl_Position = TransformWorldViewProjection(modelSpacePos); - - #if defined(PSSM) || defined(FADE) - shadowPosition = gl_Position.z; - #endif - - #ifndef POINTLIGHT - vec4 worldPos=vec4(0.0); - #endif - // get the vertex in world space - worldPos = TransformWorld(modelSpacePos); - - #ifdef DISCARD_ALPHA - texCoord = inTexCoord; - #endif - // populate the light view matrices array and convert vertex to light viewProj space - projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos; - projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos; - projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos; - projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos; - #ifdef POINTLIGHT - projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos; - projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos; - #else - #ifndef PSSM - vec3 lightDir = worldPos.xyz - m_LightPos; - lightDot = dot(m_LightDir,lightDir); - #endif - #endif -} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag index 74e59482b..b144dca60 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag @@ -18,14 +18,16 @@ uniform mat4 m_LightViewProjectionMatrix1; uniform mat4 m_LightViewProjectionMatrix2; uniform mat4 m_LightViewProjectionMatrix3; +uniform vec2 g_ResolutionInverse; + #ifdef POINTLIGHT uniform vec3 m_LightPos; uniform mat4 m_LightViewProjectionMatrix4; uniform mat4 m_LightViewProjectionMatrix5; #else + uniform vec3 m_LightDir; #ifndef PSSM - uniform vec3 m_LightPos; - uniform vec3 m_LightDir; + uniform vec3 m_LightPos; #endif #endif @@ -39,6 +41,19 @@ vec3 getPosition(in float depth, in vec2 uv){ return pos.xyz / pos.w; } +vec3 approximateNormal(in vec4 worldPos,in vec2 texCoord){ + float step = g_ResolutionInverse.x ; + float stepy = g_ResolutionInverse.y ; + float depth2 = texture2D(m_DepthTexture,texCoord + vec2(step,-stepy)).r; + float depth3 = texture2D(m_DepthTexture,texCoord + vec2(-step,-stepy)).r; + vec4 worldPos2 = vec4(getPosition(depth2,texCoord + vec2(step,-stepy)),1.0); + vec4 worldPos3 = vec4(getPosition(depth3,texCoord + vec2(-step,-stepy)),1.0); + + vec3 v1 = (worldPos - worldPos2).xyz; + vec3 v2 = (worldPos3 - worldPos2).xyz; + return normalize(cross(v1, v2)); +} + void main(){ #if !defined( RENDER_SHADOWS ) gl_FragColor = texture2D(m_Texture,texCoord); @@ -48,6 +63,7 @@ void main(){ float depth = texture2D(m_DepthTexture,texCoord).r; vec4 color = texture2D(m_Texture,texCoord); + //Discard shadow computation on the sky if(depth == 1.0){ gl_FragColor = color; @@ -56,6 +72,19 @@ void main(){ // get the vertex in world space vec4 worldPos = vec4(getPosition(depth,texCoord),1.0); + vec3 normal = approximateNormal(worldPos, texCoord); + + vec3 lightDir; + #ifdef PSSM + lightDir = m_LightDir; + #else + lightDir = worldPos.xyz - m_LightPos; + #endif + float ndotl = dot(normal, lightDir); + if(ndotl > -0.0){ + gl_FragColor = color; + return; + } #if (!defined(POINTLIGHT) && !defined(PSSM)) vec3 lightDir = worldPos.xyz - m_LightPos; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md index 9a78752ae..ced2f9fdb 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md @@ -38,13 +38,15 @@ MaterialDef Post Shadow { Texture2D Texture Texture2D DepthTexture + Boolean BackfaceShadows: true } Technique { VertexShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.vert FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.frag - WorldParameters { + WorldParameters { + ResolutionInverse } Defines { @@ -59,7 +61,7 @@ MaterialDef Post Shadow { POINTLIGHT : LightViewProjectionMatrix5 //if no shadow map don't render shadows RENDER_SHADOWS : ShadowMap0 - + BACKFACE_SHADOWS : BackfaceShadows } } @@ -68,7 +70,8 @@ MaterialDef Post Shadow { VertexShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.vert FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.frag - WorldParameters { + WorldParameters { + ResolutionInverse } Defines { @@ -79,6 +82,7 @@ MaterialDef Post Shadow { FADE : FadeInfo PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 + BACKFACE_SHADOWS : BackfaceShadows } } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag index b8dcff90f..054382cd6 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter15.frag @@ -1,5 +1,5 @@ #import "Common/ShaderLib/MultiSample.glsllib" -#import "Common/ShaderLib/Shadows15.glsllib" +#import "Common/ShaderLib/Shadows.glsllib" uniform COLORTEXTURE m_Texture; @@ -20,14 +20,16 @@ uniform mat4 m_LightViewProjectionMatrix1; uniform mat4 m_LightViewProjectionMatrix2; uniform mat4 m_LightViewProjectionMatrix3; +uniform vec2 g_ResolutionInverse; + #ifdef POINTLIGHT uniform vec3 m_LightPos; uniform mat4 m_LightViewProjectionMatrix4; uniform mat4 m_LightViewProjectionMatrix5; #else + uniform vec3 m_LightDir; #ifndef PSSM - uniform vec3 m_LightPos; - uniform vec3 m_LightDir; + uniform vec3 m_LightPos; #endif #endif @@ -41,6 +43,23 @@ vec3 getPosition(in float depth, in vec2 uv){ return pos.xyz / pos.w; } +#ifndef BACKFACE_SHADOWS + vec3 approximateNormal(in float depth,in vec4 worldPos,in vec2 texCoord, in int numSample){ + float step = g_ResolutionInverse.x ; + float stepy = g_ResolutionInverse.y ; + float depth1 = fetchTextureSample(m_DepthTexture,texCoord + vec2(-step,stepy),numSample).r; + float depth2 = fetchTextureSample(m_DepthTexture,texCoord + vec2(step,stepy),numSample).r; + vec3 v1, v2; + vec4 worldPos1 = vec4(getPosition(depth1,texCoord + vec2(-step,stepy)),1.0); + vec4 worldPos2 = vec4(getPosition(depth2,texCoord + vec2(step,stepy)),1.0); + + v1 = normalize((worldPos1 - worldPos)).xyz; + v2 = normalize((worldPos2 - worldPos)).xyz; + return normalize(cross(v2, v1)); + + } +#endif + vec4 main_multiSample(in int numSample){ float depth = fetchTextureSample(m_DepthTexture,texCoord,numSample).r;//getDepth(m_DepthTexture,texCoord).r; vec4 color = fetchTextureSample(m_Texture,texCoord,numSample); @@ -52,12 +71,27 @@ vec4 main_multiSample(in int numSample){ // get the vertex in world space vec4 worldPos = vec4(getPosition(depth,texCoord),1.0); - + + + vec3 lightDir; + #ifdef PSSM + lightDir = m_LightDir; + #else + lightDir = worldPos.xyz - m_LightPos; + #endif + + #ifndef BACKFACE_SHADOWS + vec3 normal = approximateNormal(depth, worldPos, texCoord, numSample); + float ndotl = dot(normal, lightDir); + if(ndotl > 0.0){ + return color; + } + #endif + #if (!defined(POINTLIGHT) && !defined(PSSM)) - vec3 lightDir = worldPos.xyz - m_LightPos; - if( dot(m_LightDir,lightDir)<0){ - return color; - } + if( dot(m_LightDir,lightDir)<0){ + return color; + } #endif // populate the light view matrices array and convert vertex to light viewProj space diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib index d2f1a942b..9934db637 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib @@ -1,22 +1,57 @@ -#ifdef HARDWARE_SHADOWS - #define SHADOWMAP sampler2DShadow - #define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r -#else - #define SHADOWMAP sampler2D - #define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r) -#endif +#if __VERSION__ >= 130 + // Because gpu_shader5 is actually where those + // gather functions are declared to work on shadowmaps + #extension GL_ARB_gpu_shader5 : enable + #define IVEC2 ivec2 + #ifdef HARDWARE_SHADOWS + #define SHADOWMAP sampler2DShadow + #define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset) + #define SHADOWCOMPARE(tex,coord) textureProj(tex, coord) + #define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z) + #else + #define SHADOWMAP sampler2D + #define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r) + #define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r) + #define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy)) + #endif -#if FILTER_MODE == 0 - #define GETSHADOW Shadow_DoShadowCompare - #define KERNEL 1.0 -#elif FILTER_MODE == 1 + #if FILTER_MODE == 0 + #define GETSHADOW Shadow_Nearest + #define KERNEL 1.0 + #elif FILTER_MODE == 1 + #ifdef HARDWARE_SHADOWS + #define GETSHADOW Shadow_Nearest + #else + #define GETSHADOW Shadow_DoBilinear_2x2 + #endif + #define KERNEL 1.0 + #endif +#else + #define IVEC2 vec2 #ifdef HARDWARE_SHADOWS - #define GETSHADOW Shadow_DoShadowCompare + #define SHADOWMAP sampler2DShadow + #define SHADOWCOMPARE(tex,coord) shadow2DProj(tex, coord).r #else - #define GETSHADOW Shadow_DoBilinear_2x2 + #define SHADOWMAP sampler2D + #define SHADOWCOMPARE(tex,coord) step(coord.z, texture2DProj(tex, coord).r) #endif - #define KERNEL 1.0 -#elif FILTER_MODE == 2 + + #if FILTER_MODE == 0 + #define GETSHADOW Shadow_DoShadowCompare + #define KERNEL 1.0 + #elif FILTER_MODE == 1 + #ifdef HARDWARE_SHADOWS + #define GETSHADOW Shadow_DoShadowCompare + #else + #define GETSHADOW Shadow_DoBilinear_2x2 + #endif + #define KERNEL 1.0 + #endif + + +#endif + +#if FILTER_MODE == 2 #define GETSHADOW Shadow_DoDither_2x2 #define KERNEL 1.0 #elif FILTER_MODE == 3 @@ -30,14 +65,13 @@ #define KERNEL 8.0 #endif - uniform SHADOWMAP m_ShadowMap0; uniform SHADOWMAP m_ShadowMap1; uniform SHADOWMAP m_ShadowMap2; uniform SHADOWMAP m_ShadowMap3; #ifdef POINTLIGHT -uniform SHADOWMAP m_ShadowMap4; -uniform SHADOWMAP m_ShadowMap5; + uniform SHADOWMAP m_ShadowMap4; + uniform SHADOWMAP m_ShadowMap5; #endif #ifdef PSSM @@ -49,73 +83,91 @@ uniform float m_ShadowIntensity; const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE); float shadowBorderScale = 1.0; -float Shadow_DoShadowCompareOffset(SHADOWMAP tex, vec4 projCoord, vec2 offset){ - vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw); - return SHADOWCOMPARE(tex, coord); -} - -float Shadow_DoShadowCompare(SHADOWMAP tex, vec4 projCoord){ +float Shadow_DoShadowCompare(in SHADOWMAP tex,in vec4 projCoord){ return SHADOWCOMPARE(tex, projCoord); } -float Shadow_BorderCheck(vec2 coord){ +float Shadow_BorderCheck(in vec2 coord){ // Fastest, "hack" method (uses 4-5 instructions) vec4 t = vec4(coord.xy, 0.0, 1.0); t = step(t.wwxy, t.xyzz); return dot(t,t); } -float Shadow_Nearest(SHADOWMAP tex, vec4 projCoord){ +float Shadow_Nearest(in SHADOWMAP tex,in vec4 projCoord){ float border = Shadow_BorderCheck(projCoord.xy); if (border > 0.0){ return 1.0; } - return Shadow_DoShadowCompare(tex,projCoord); + return SHADOWCOMPARE(tex, projCoord); +} + +float Shadow_DoShadowCompareOffset(in SHADOWMAP tex,in vec4 projCoord,in vec2 offset){ + vec4 coord = vec4(projCoord.xy + offset.xy * pixSize2 * shadowBorderScale, projCoord.zw); + return SHADOWCOMPARE(tex, coord); } -float Shadow_DoDither_2x2(SHADOWMAP tex, vec4 projCoord){ + +float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){ float border = Shadow_BorderCheck(projCoord.xy); if (border > 0.0) return 1.0; - float shadow = 0.0; - vec2 o = mod(floor(gl_FragCoord.xy), 2.0); - shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, 1.5) + o); - shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, 1.5) + o); - shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2(-1.5, -0.5) + o); - shadow += Shadow_DoShadowCompareOffset(tex,projCoord,vec2( 0.5, -0.5) + o); - shadow *= 0.25 ; + IVEC2 o = IVEC2(mod(floor(gl_FragCoord.xy), 2.0)); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, 1.5)+o)); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, 1.5)+o)); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2(-1.5, -0.5)+o)); + shadow += Shadow_DoShadowCompareOffset(tex, projCoord, (vec2( 0.5, -0.5)+o)); + shadow *= 0.25; return shadow; } -float Shadow_DoBilinear_2x2(SHADOWMAP tex, vec4 projCoord){ +float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){ float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0) + if (border > 0.0){ return 1.0; + } + vec4 gather = vec4(0.0); - gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0)); - gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0)); - gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0)); - gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0)); - - vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE ); - vec2 mx = mix( gather.xz, gather.yw, f.x ); - return mix( mx.x, mx.y, f.y ); + #if __VERSION__ >= 130 + #ifdef GL_ARB_gpu_shader5 + vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0); + gather = SHADOWGATHER(tex, coord); + #else + gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1)); + gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1)); + gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0)); + gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 0)); + #endif + #else + gather.x = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 0.0)); + gather.y = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 0.0)); + gather.z = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(0.0, 1.0)); + gather.w = Shadow_DoShadowCompareOffset(tex, projCoord, vec2(1.0, 1.0)); + #endif + + vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE ); + vec2 mx = mix( gather.wx, gather.zy, f.x ); + return mix( mx.x, mx.y, f.y ); } -float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){ +float Shadow_DoPCF(in SHADOWMAP tex,in vec4 projCoord){ + float shadow = 0.0; float border = Shadow_BorderCheck(projCoord.xy); if (border > 0.0) return 1.0; + float bound = KERNEL * 0.5 - 0.5; bound *= PCFEDGE; for (float y = -bound; y <= bound; y += PCFEDGE){ for (float x = -bound; x <= bound; x += PCFEDGE){ - shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) + - border, - 0.0, 1.0); + #if __VERSION__ < 130 + shadow += clamp(Shadow_DoShadowCompareOffset(tex,projCoord,vec2(x,y)) + border, 0.0, 1.0); + #else + shadow += Shadow_DoShadowCompareOffset(tex, projCoord, vec2(x,y)); + #endif } } @@ -123,51 +175,51 @@ float Shadow_DoPCF(SHADOWMAP tex, vec4 projCoord){ return shadow; } - //12 tap poisson disk - const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016); - const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201); - const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521); - const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819); - const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957); - const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841); - const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516); - const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126); - const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693); - const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707); - const vec2 poissonDisk10 = vec2(0.5653144, 0.60262); - const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419); - -float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){ +const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016); +const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201); +const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521); +const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819); +const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957); +const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841); +const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516); +const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126); +const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693); +const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707); +const vec2 poissonDisk10 = vec2(0.5653144, 0.60262); +const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419); + + +float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){ float shadow = 0.0; float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0) + if (border > 0.0){ return 1.0; + } - vec2 texelSize = vec2( 4.0 * PCFEDGE * shadowBorderScale); - - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk0 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk1 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk2 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk3 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk4 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk5 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk6 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk7 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk8 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk9 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk10 * texelSize); - shadow += Shadow_DoShadowCompareOffset(tex, projCoord , poissonDisk11 * texelSize); - - shadow = shadow * 0.08333333333;//this is divided by 12 - return shadow; -} + vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale; + + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw)); + shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw)); + //this is divided by 12 + return shadow * 0.08333333333; +} -#ifdef POINTLIGHT - float getPointLightShadows(vec4 worldPos,vec3 lightPos, - SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3,SHADOWMAP shadowMap4,SHADOWMAP shadowMap5, - vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3,vec4 projCoord4,vec4 projCoord5){ +#ifdef POINTLIGHT + float getPointLightShadows(in vec4 worldPos,in vec3 lightPos, + in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5, + in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){ float shadow = 1.0; vec3 vect = worldPos.xyz - lightPos; vec3 absv= abs(vect); @@ -190,42 +242,41 @@ float Shadow_DoPCFPoisson(SHADOWMAP tex, vec4 projCoord){ }else{ shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w); } - } + } return shadow; } #else #ifdef PSSM - float getDirectionalLightShadows(vec4 splits,float shadowPosition, - SHADOWMAP shadowMap0,SHADOWMAP shadowMap1,SHADOWMAP shadowMap2,SHADOWMAP shadowMap3, - vec4 projCoord0,vec4 projCoord1,vec4 projCoord2,vec4 projCoord3){ - float shadow = 1.0; + float getDirectionalLightShadows(in vec4 splits,in float shadowPosition, + in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3, + in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){ + float shadow = 1.0; if(shadowPosition < splits.x){ - shadow = GETSHADOW(shadowMap0, projCoord0 ); + shadow = GETSHADOW(shadowMap0, projCoord0 ); }else if( shadowPosition < splits.y){ shadowBorderScale = 0.5; - shadow = GETSHADOW(shadowMap1, projCoord1); + shadow = GETSHADOW(shadowMap1, projCoord1); }else if( shadowPosition < splits.z){ shadowBorderScale = 0.25; - shadow = GETSHADOW(shadowMap2, projCoord2); + shadow = GETSHADOW(shadowMap2, projCoord2); }else if( shadowPosition < splits.w){ shadowBorderScale = 0.125; - shadow = GETSHADOW(shadowMap3, projCoord3); + shadow = GETSHADOW(shadowMap3, projCoord3); } return shadow; } #else - float getSpotLightShadows(SHADOWMAP shadowMap, vec4 projCoord){ - float shadow = 1.0; + float getSpotLightShadows(in SHADOWMAP shadowMap,in vec4 projCoord){ + float shadow = 1.0; projCoord /= projCoord.w; - shadow = GETSHADOW(shadowMap, projCoord); - + shadow = GETSHADOW(shadowMap,projCoord); + //a small falloff to make the shadow blend nicely into the not lighten - //we translate the texture coordinate value to a -1,1 range so the length + //we translate the texture coordinate value to a -1,1 range so the length //of the texture coordinate vector is actually the radius of the lighten area on the ground projCoord = projCoord * 2.0 - 1.0; float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1; return mix(shadow,1.0,clamp(fallOff,0.0,1.0)); - } #endif #endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib deleted file mode 100644 index 5c9e0fa1c..000000000 --- a/jme3-core/src/main/resources/Common/ShaderLib/Shadows15.glsllib +++ /dev/null @@ -1,242 +0,0 @@ -// Because gpu_shader5 is actually where those -// gather functions are declared to work on shadowmaps -#extension GL_ARB_gpu_shader5 : enable - -#ifdef HARDWARE_SHADOWS - #define SHADOWMAP sampler2DShadow - #define SHADOWCOMPAREOFFSET(tex,coord,offset) textureProjOffset(tex, coord, offset) - #define SHADOWCOMPARE(tex,coord) textureProj(tex, coord) - #define SHADOWGATHER(tex,coord) textureGather(tex, coord.xy, coord.z) -#else - #define SHADOWMAP sampler2D - #define SHADOWCOMPAREOFFSET(tex,coord,offset) step(coord.z, textureProjOffset(tex, coord, offset).r) - #define SHADOWCOMPARE(tex,coord) step(coord.z, textureProj(tex, coord).r) - #define SHADOWGATHER(tex,coord) step(coord.z, textureGather(tex, coord.xy)) -#endif - - -#if FILTER_MODE == 0 - #define GETSHADOW Shadow_Nearest - #define KERNEL 1.0 -#elif FILTER_MODE == 1 - #ifdef HARDWARE_SHADOWS - #define GETSHADOW Shadow_Nearest - #else - #define GETSHADOW Shadow_DoBilinear_2x2 - #endif - #define KERNEL 1.0 -#elif FILTER_MODE == 2 - #define GETSHADOW Shadow_DoDither_2x2 - #define KERNEL 1.0 -#elif FILTER_MODE == 3 - #define GETSHADOW Shadow_DoPCF - #define KERNEL 4.0 -#elif FILTER_MODE == 4 - #define GETSHADOW Shadow_DoPCFPoisson - #define KERNEL 4.0 -#elif FILTER_MODE == 5 - #define GETSHADOW Shadow_DoPCF - #define KERNEL 8.0 -#endif - - - -uniform SHADOWMAP m_ShadowMap0; -uniform SHADOWMAP m_ShadowMap1; -uniform SHADOWMAP m_ShadowMap2; -uniform SHADOWMAP m_ShadowMap3; -#ifdef POINTLIGHT -uniform SHADOWMAP m_ShadowMap4; -uniform SHADOWMAP m_ShadowMap5; -#endif - -#ifdef PSSM -uniform vec4 m_Splits; -#endif -uniform float m_ShadowIntensity; - -const vec2 pixSize2 = vec2(1.0 / SHADOWMAP_SIZE); -float shadowBorderScale = 1.0; - -float Shadow_BorderCheck(in vec2 coord){ - // Fastest, "hack" method (uses 4-5 instructions) - vec4 t = vec4(coord.xy, 0.0, 1.0); - t = step(t.wwxy, t.xyzz); - return dot(t,t); -} - -float Shadow_Nearest(in SHADOWMAP tex, in vec4 projCoord){ - float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0){ - return 1.0; - } - return SHADOWCOMPARE(tex,projCoord); -} - -float Shadow_DoDither_2x2(in SHADOWMAP tex, in vec4 projCoord){ - float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0) - return 1.0; - - vec2 pixSize = pixSize2 * shadowBorderScale; - - float shadow = 0.0; - ivec2 o = ivec2(mod(floor(gl_FragCoord.xy), 2.0)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, 1.5)+o), projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, 1.5)+o), projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2(-1.5, -0.5)+o), projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy+pixSize*(vec2( 0.5, -0.5)+o), projCoord.zw)); - shadow *= 0.25; - return shadow; -} - -float Shadow_DoBilinear_2x2(in SHADOWMAP tex, in vec4 projCoord){ - float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0) - return 1.0; - - #ifdef GL_ARB_gpu_shader5 - vec4 coord = vec4(projCoord.xyz / projCoord.www,0.0); - vec4 gather = SHADOWGATHER(tex, coord); - #else - vec4 gather = vec4(0.0); - gather.x = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 1)); - gather.y = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 1)); - gather.z = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(1, 0)); - gather.w = SHADOWCOMPAREOFFSET(tex, projCoord, ivec2(0, 0)); - #endif - - vec2 f = fract( projCoord.xy * SHADOWMAP_SIZE ); - vec2 mx = mix( gather.wx, gather.zy, f.x ); - return mix( mx.x, mx.y, f.y ); -} - -float Shadow_DoPCF(in SHADOWMAP tex, in vec4 projCoord){ - - vec2 pixSize = pixSize2 * shadowBorderScale; - float shadow = 0.0; - float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0) - return 1.0; - - float bound = KERNEL * 0.5 - 0.5; - bound *= PCFEDGE; - for (float y = -bound; y <= bound; y += PCFEDGE){ - for (float x = -bound; x <= bound; x += PCFEDGE){ - vec4 coord = vec4(projCoord.xy + vec2(x,y) * pixSize, projCoord.zw); - shadow += SHADOWCOMPARE(tex, coord); - } - } - - shadow = shadow / (KERNEL * KERNEL); - return shadow; -} - - -//12 tap poisson disk - const vec2 poissonDisk0 = vec2(-0.1711046, -0.425016); - const vec2 poissonDisk1 = vec2(-0.7829809, 0.2162201); - const vec2 poissonDisk2 = vec2(-0.2380269, -0.8835521); - const vec2 poissonDisk3 = vec2(0.4198045, 0.1687819); - const vec2 poissonDisk4 = vec2(-0.684418, -0.3186957); - const vec2 poissonDisk5 = vec2(0.6026866, -0.2587841); - const vec2 poissonDisk6 = vec2(-0.2412762, 0.3913516); - const vec2 poissonDisk7 = vec2(0.4720655, -0.7664126); - const vec2 poissonDisk8 = vec2(0.9571564, 0.2680693); - const vec2 poissonDisk9 = vec2(-0.5238616, 0.802707); - const vec2 poissonDisk10 = vec2(0.5653144, 0.60262); - const vec2 poissonDisk11 = vec2(0.0123658, 0.8627419); - - -float Shadow_DoPCFPoisson(in SHADOWMAP tex, in vec4 projCoord){ - - float shadow = 0.0; - float border = Shadow_BorderCheck(projCoord.xy); - if (border > 0.0){ - return 1.0; - } - - vec2 texelSize = pixSize2 * 4.0 * PCFEDGE * shadowBorderScale; - - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk0 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk1 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk2 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk3 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk4 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk5 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk6 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk7 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk8 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk9 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk10 * texelSize, projCoord.zw)); - shadow += SHADOWCOMPARE(tex, vec4(projCoord.xy + poissonDisk11 * texelSize, projCoord.zw)); - - //this is divided by 12 - return shadow * 0.08333333333; -} - -#ifdef POINTLIGHT - float getPointLightShadows(in vec4 worldPos,in vec3 lightPos, - in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3,in SHADOWMAP shadowMap4,in SHADOWMAP shadowMap5, - in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3,in vec4 projCoord4,in vec4 projCoord5){ - float shadow = 1.0; - vec3 vect = worldPos.xyz - lightPos; - vec3 absv= abs(vect); - float maxComp = max(absv.x,max(absv.y,absv.z)); - if(maxComp == absv.y){ - if(vect.y < 0.0){ - shadow = GETSHADOW(shadowMap0, projCoord0 / projCoord0.w); - }else{ - shadow = GETSHADOW(shadowMap1, projCoord1 / projCoord1.w); - } - }else if(maxComp == absv.z){ - if(vect.z < 0.0){ - shadow = GETSHADOW(shadowMap2, projCoord2 / projCoord2.w); - }else{ - shadow = GETSHADOW(shadowMap3, projCoord3 / projCoord3.w); - } - }else if(maxComp == absv.x){ - if(vect.x < 0.0){ - shadow = GETSHADOW(shadowMap4, projCoord4 / projCoord4.w); - }else{ - shadow = GETSHADOW(shadowMap5, projCoord5 / projCoord5.w); - } - } - return shadow; - } -#else - #ifdef PSSM - float getDirectionalLightShadows(in vec4 splits,in float shadowPosition, - in SHADOWMAP shadowMap0,in SHADOWMAP shadowMap1,in SHADOWMAP shadowMap2,in SHADOWMAP shadowMap3, - in vec4 projCoord0,in vec4 projCoord1,in vec4 projCoord2,in vec4 projCoord3){ - float shadow = 1.0; - if(shadowPosition < splits.x){ - shadow = GETSHADOW(shadowMap0, projCoord0 ); - }else if( shadowPosition < splits.y){ - shadowBorderScale = 0.5; - shadow = GETSHADOW(shadowMap1, projCoord1); - }else if( shadowPosition < splits.z){ - shadowBorderScale = 0.25; - shadow = GETSHADOW(shadowMap2, projCoord2); - }else if( shadowPosition < splits.w){ - shadowBorderScale = 0.125; - shadow = GETSHADOW(shadowMap3, projCoord3); - } - return shadow; - } - #else - float getSpotLightShadows(in SHADOWMAP shadowMap,in vec4 projCoord){ - float shadow = 1.0; - projCoord /= projCoord.w; - shadow = GETSHADOW(shadowMap,projCoord); - - //a small falloff to make the shadow blend nicely into the not lighten - //we translate the texture coordinate value to a -1,1 range so the length - //of the texture coordinate vector is actually the radius of the lighten area on the ground - projCoord = projCoord * 2.0 - 1.0; - float fallOff = ( length(projCoord.xy) - 0.9 ) / 0.1; - return mix(shadow,1.0,clamp(fallOff,0.0,1.0)); - - } - #endif -#endif diff --git a/jme3-core/src/main/resources/com/jme3/system/version.properties b/jme3-core/src/main/resources/com/jme3/system/version.properties index 98168a14e..ae20006c0 100644 --- a/jme3-core/src/main/resources/com/jme3/system/version.properties +++ b/jme3-core/src/main/resources/com/jme3/system/version.properties @@ -1,11 +1,12 @@ # THIS IS AN AUTO-GENERATED FILE.. # DO NOT MODIFY! -build.date=1900-01-01 +build.date=2016-03-25 git.revision=0 git.branch=unknown git.hash= git.hash.short= git.tag= name.full=jMonkeyEngine 3.1.0-UNKNOWN +version.full=3.1.0-UNKNOWN version.number=3.1.0 version.tag=SNAPSHOT \ No newline at end of file diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index 9762635a1..70f918a6b 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java @@ -180,9 +180,23 @@ public class J3MLoader implements AssetLoader { return matchList; } - private boolean isTexturePathDeclaredTheTraditionalWay(final int numberOfValues, final int numberOfTextureOptions, final String texturePath) { - return (numberOfValues > 1 && (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Flip ") || - texturePath.startsWith("Repeat ") || texturePath.startsWith("Repeat Flip "))) || numberOfTextureOptions == 0; + private boolean isTexturePathDeclaredTheTraditionalWay(final List optionValues, final String texturePath) { + final boolean startsWithOldStyle = texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Flip ") || + texturePath.startsWith("Repeat ") || texturePath.startsWith("Repeat Flip "); + + if (!startsWithOldStyle) { + return false; + } + + if (optionValues.size() == 1 && (optionValues.get(0).textureOption == TextureOption.Flip || optionValues.get(0).textureOption == TextureOption.Repeat)) { + return true; + } else if (optionValues.size() == 2 && optionValues.get(0).textureOption == TextureOption.Flip && optionValues.get(1).textureOption == TextureOption.Repeat) { + return true; + } else if (optionValues.size() == 2 && optionValues.get(0).textureOption == TextureOption.Repeat && optionValues.get(1).textureOption == TextureOption.Flip) { + return true; + } + + return false; } private Texture parseTextureType(final VarType type, final String value) { @@ -198,7 +212,7 @@ public class J3MLoader implements AssetLoader { String texturePath = value.trim(); // If there are no valid "new" texture options specified but the path is split into several parts, lets parse the old way. - if (isTexturePathDeclaredTheTraditionalWay(textureValues.size(), textureOptionValues.size(), texturePath)) { + if (isTexturePathDeclaredTheTraditionalWay(textureOptionValues, texturePath)) { boolean flipY = false; if (texturePath.startsWith("Flip Repeat ") || texturePath.startsWith("Repeat Flip ")) { @@ -455,6 +469,8 @@ public class J3MLoader implements AssetLoader { renderState.setDepthFunc(RenderState.TestFunction.valueOf(split[1])); }else if (split[0].equals("AlphaFunc")){ renderState.setAlphaFunc(RenderState.TestFunction.valueOf(split[1])); + }else if (split[0].equals("LineWidth")){ + renderState.setLineWidth(Float.parseFloat(split[1])); } else { throw new MatParseException(null, split[0], statement); } @@ -636,6 +652,7 @@ public class J3MLoader implements AssetLoader { material = new Material(def); material.setKey(key); + material.setName(split[0].trim()); // material.setAssetName(fileName); }else if (split.length == 1){ if (extending){ diff --git a/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java index 6438baaac..f45eb9bdf 100644 --- a/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java +++ b/jme3-core/src/test/java/com/jme3/material/plugins/J3MLoaderTest.java @@ -80,6 +80,7 @@ public class J3MLoaderTest { final Texture textureMin = Mockito.mock(Texture.class); final Texture textureMag = Mockito.mock(Texture.class); final Texture textureCombined = Mockito.mock(Texture.class); + final Texture textureLooksLikeOldStyle = Mockito.mock(Texture.class); final TextureKey textureKeyNoParameters = setupMockForTexture("Empty", "empty.png", false, textureNoParameters); final TextureKey textureKeyFlip = setupMockForTexture("Flip", "flip.png", true, textureFlip); @@ -88,6 +89,7 @@ public class J3MLoaderTest { setupMockForTexture("Min", "min.png", false, textureMin); setupMockForTexture("Mag", "mag.png", false, textureMag); setupMockForTexture("Combined", "combined.png", true, textureCombined); + setupMockForTexture("LooksLikeOldStyle", "oldstyle.png", true, textureLooksLikeOldStyle); j3MLoader.load(assetInfo); diff --git a/jme3-core/src/test/resources/texture-parameters-newstyle.j3m b/jme3-core/src/test/resources/texture-parameters-newstyle.j3m index a7619d948..e4b15ad4a 100644 --- a/jme3-core/src/test/resources/texture-parameters-newstyle.j3m +++ b/jme3-core/src/test/resources/texture-parameters-newstyle.j3m @@ -6,6 +6,7 @@ Material Test : matdef.j3md { Min: MinTrilinear "min.png" Mag: MagBilinear "mag.png" RepeatAxis: WrapRepeat_T "repeat-axis.png" - Combined: MagNearest MinBilinearNoMipMaps Flip WrapRepeat "combined.png" + Combined: Flip MagNearest MinBilinearNoMipMaps WrapRepeat "combined.png" + LooksLikeOldStyle: Flip WrapRepeat "oldstyle.png" } } \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert index 00aa103ff..3b09ec310 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert +++ b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert @@ -20,5 +20,5 @@ void main(void) Skinning_Compute(modelSpacePos,modelSpaceNormals); #endif normal = normalize(TransformNormal(modelSpaceNormals)); - gl_Position = g_WorldViewProjectionMatrix * modelSpacePos; + gl_Position = TransformWorldViewProjection(modelSpacePos); } \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/TestChooser.java b/jme3-examples/src/main/java/jme3test/TestChooser.java index a2251945e..f5f54958e 100644 --- a/jme3-examples/src/main/java/jme3test/TestChooser.java +++ b/jme3-examples/src/main/java/jme3test/TestChooser.java @@ -260,8 +260,8 @@ public class TestChooser extends JDialog { for (int i = 0; i < appClass.length; i++) { Class clazz = (Class)appClass[i]; try { - Object app = clazz.newInstance(); - if (app instanceof Application) { + if (Application.class.isAssignableFrom(clazz)) { + Object app = clazz.newInstance(); if (app instanceof SimpleApplication) { final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class); settingMethod.invoke(app, showSetting); @@ -283,7 +283,7 @@ public class TestChooser extends JDialog { } } else { final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass()); - mainMethod.invoke(app, new Object[]{new String[0]}); + mainMethod.invoke(clazz, new Object[]{new String[0]}); } // wait for destroy System.gc(); diff --git a/jme3-examples/src/main/java/jme3test/app/TestCloner.java b/jme3-examples/src/main/java/jme3test/app/TestCloner.java new file mode 100644 index 000000000..4ae105603 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestCloner.java @@ -0,0 +1,367 @@ +/* + * Copyright (c) 2016 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.app; + +import java.util.*; + +import com.jme3.util.clone.*; + +/** + * + * + * @author Paul Speed + */ +public class TestCloner { + + public static void main( String... args ) { + + System.out.println("Clone test:"); + + Cloner cloner = new Cloner(); + + RegularObject ro = new RegularObject(42); + System.out.println("Regular Object:" + ro); + RegularObject roCloneLegacy = ro.clone(); + System.out.println("Regular Object Clone:" + roCloneLegacy); + RegularObject roClone = cloner.clone(ro); + System.out.println("cloner: Regular Object Clone:" + roClone); + + System.out.println("------------------------------------"); + System.out.println(); + + cloner = new Cloner(); + RegularSubclass rsc = new RegularSubclass(69, "test"); + System.out.println("Regular subclass:" + rsc); + RegularSubclass rscCloneLegacy = (RegularSubclass)rsc.clone(); + System.out.println("Regular subclass Clone:" + rscCloneLegacy); + RegularSubclass rscClone = cloner.clone(rsc); + System.out.println("cloner: Regular subclass Clone:" + rscClone); + + System.out.println("------------------------------------"); + System.out.println(); + + cloner = new Cloner(); + Parent parent = new Parent("Foo", 34); + System.out.println("Parent:" + parent); + Parent parentCloneLegacy = parent.clone(); + System.out.println("Parent Clone:" + parentCloneLegacy); + Parent parentClone = cloner.clone(parent); + System.out.println("cloner: Parent Clone:" + parentClone); + + System.out.println("------------------------------------"); + System.out.println(); + + cloner = new Cloner(); + GraphNode root = new GraphNode("root"); + GraphNode child1 = root.addLink("child1"); + GraphNode child2 = root.addLink("child2"); + GraphNode shared = child1.addLink("shared"); + child2.addLink(shared); + + // Add a circular reference to get fancy + shared.addLink(root); + + System.out.println("Simple graph:"); + root.dump(" "); + + GraphNode rootClone = cloner.clone(root); + System.out.println("clone:"); + rootClone.dump(" "); + + System.out.println("original:"); + root.dump(" "); + + GraphNode reclone = Cloner.deepClone(root); + System.out.println("reclone:"); + reclone.dump(" "); + + System.out.println("------------------------------------"); + System.out.println(); + cloner = new Cloner(); + + ArrayHolder arrays = new ArrayHolder(5, 3, 7, 3, 7, 2, 1, 4); + System.out.println("Array holder:" + arrays); + ArrayHolder arraysClone = cloner.clone(arrays); + System.out.println("Array holder clone:" + arraysClone); + + + + } + + public static class RegularObject implements Cloneable { + protected int i; + + public RegularObject( int i ) { + this.i = i; + } + + public RegularObject clone() { + try { + return (RegularObject)super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException(e); + } + } + + public String toString() { + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + + "[i=" + i + "]"; + } + } + + public static class RegularSubclass extends RegularObject { + protected String name; + + public RegularSubclass( int i, String name ) { + super(i); + this.name = name; + } + + public String toString() { + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + + "[i=" + i + ", name=" + name + "]"; + } + } + + public static class Parent implements Cloneable, JmeCloneable { + + private RegularObject ro; + private RegularSubclass rsc; + + public Parent( String name, int age ) { + this.ro = new RegularObject(age); + this.rsc = new RegularSubclass(age, name); + } + + public Parent clone() { + try { + return (Parent)super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException(e); + } + } + + public Parent jmeClone() { + // Ok to delegate to clone() in this case because no deep + // cloning is done there. + return clone(); + } + + public void cloneFields( Cloner cloner, Object original ) { + this.ro = cloner.clone(ro); + this.rsc = cloner.clone(rsc); + } + + public String toString() { + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + + "[ro=" + ro + ", rsc=" + rsc + "]"; + } + } + + public static class GraphNode implements Cloneable, JmeCloneable { + + private String name; + private List links = new ArrayList<>(); + + public GraphNode( String name ) { + this.name = name; + } + + public void dump( String indent ) { + dump(indent, new HashSet()); + } + + private void dump( String indent, Set visited ) { + if( visited.contains(this) ) { + // already been here + System.out.println(indent + this + " ** circular."); + return; + } + System.out.println(indent + this); + visited.add(this); + for( GraphNode n : links ) { + n.dump(indent + " ", visited); + } + visited.remove(this); + } + + public GraphNode addLink( String name ) { + GraphNode node = new GraphNode(name); + links.add(node); + return node; + } + + public GraphNode addLink( GraphNode node ) { + links.add(node); + return node; + } + + public List getLinks() { + return links; + } + + public GraphNode jmeClone() { + try { + return (GraphNode)super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException(e); + } + } + + public void cloneFields( Cloner cloner, Object original ) { + this.links = cloner.clone(links); + } + + public String toString() { + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + + "[name=" + name + "]"; + } + } + + public static class ArrayHolder implements JmeCloneable { + + private int[] intArray; + private int[][] intArray2D; + private Object[] objects; + private RegularObject[] regularObjects; + private String[] strings; + + public ArrayHolder( int... values ) { + this.intArray = values; + this.intArray2D = new int[values.length][2]; + for( int i = 0; i < values.length; i++ ) { + intArray2D[i][0] = values[i] + 1; + intArray2D[i][1] = values[i] * 2; + } + this.objects = new Object[values.length]; + this.regularObjects = new RegularObject[values.length]; + this.strings = new String[values.length]; + for( int i = 0; i < values.length; i++ ) { + objects[i] = values[i]; + regularObjects[i] = new RegularObject(values[i]); + strings[i] = String.valueOf(values[i]); + } + } + + public ArrayHolder jmeClone() { + try { + return (ArrayHolder)super.clone(); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException(e); + } + } + + public void cloneFields( Cloner cloner, Object original ) { + intArray = cloner.clone(intArray); + intArray2D = cloner.clone(intArray2D); + + // Boxed types are not cloneable so this will fail + //objects = cloner.clone(objects); + + regularObjects = cloner.clone(regularObjects); + + // Strings are also not cloneable + //strings = cloner.clone(strings); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("intArray=" + intArray); + for( int i = 0; i < intArray.length; i++ ) { + if( i == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append(intArray[i]); + } + sb.append("], "); + + sb.append("intArray2D=" + intArray2D); + for( int i = 0; i < intArray2D.length; i++ ) { + if( i == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append("intArray2D[" + i + "]=" + intArray2D[i]); + for( int j = 0; j < 2; j++ ) { + if( j == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append(intArray2D[i][j]); + } + sb.append("], "); + } + sb.append("], "); + + sb.append("objectArray=" + objects); + for( int i = 0; i < objects.length; i++ ) { + if( i == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append(objects[i]); + } + sb.append("], "); + + sb.append("objectArray=" + regularObjects); + for( int i = 0; i < regularObjects.length; i++ ) { + if( i == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append(regularObjects[i]); + } + sb.append("], "); + + sb.append("stringArray=" + strings); + for( int i = 0; i < strings.length; i++ ) { + if( i == 0 ) { + sb.append("["); + } else { + sb.append(", "); + } + sb.append(strings[i]); + } + sb.append("]"); + + return getClass().getSimpleName() + "@" + System.identityHashCode(this) + + "[" + sb + "]"; + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java b/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java index 469fb03ae..a286501bb 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java +++ b/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java @@ -46,13 +46,15 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; /** * PhysicsHoverControl uses a RayCast Vehicle with "slippery wheels" to simulate a hovering tank * @author normenhansen */ -public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsControl, PhysicsTickListener { +public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsControl, PhysicsTickListener, JmeCloneable { protected Spatial spatial; protected boolean enabled = true; @@ -94,10 +96,21 @@ public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsContro createWheels(); } + @Override public Control cloneForSpatial(Spatial spatial) { throw new UnsupportedOperationException("Not supported yet."); } + @Override + public Object jmeClone() { + throw new UnsupportedOperationException("Not yet implemented."); + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + throw new UnsupportedOperationException("Not yet implemented."); + } + public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); @@ -179,6 +192,7 @@ public class PhysicsHoverControl extends PhysicsVehicle implements PhysicsContro } public void setPhysicsSpace(PhysicsSpace space) { + createVehicle(space); if (space == null) { if (this.space != null) { this.space.removeCollisionObject(this); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java b/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java index 5f058e68b..d1def4203 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java @@ -147,13 +147,13 @@ public class TestHoveringTank extends SimpleApplication implements AnalogListene spaceCraft.setLocalRotation(new Quaternion(new float[]{0, 0.01f, 0})); hoverControl = new PhysicsHoverControl(colShape, 500); - hoverControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02); spaceCraft.addControl(hoverControl); rootNode.attachChild(spaceCraft); getPhysicsSpace().add(hoverControl); + hoverControl.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02); ChaseCamera chaseCam = new ChaseCamera(cam, inputManager); spaceCraft.addControl(chaseCam); diff --git a/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java index 84d186e3a..f86f956b6 100644 --- a/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java +++ b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java @@ -128,12 +128,12 @@ public class TestMousePick extends SimpleApplication { /** A red ball that marks the last spot that was "hit" by the "shot". */ protected void initMark() { Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f)); - arrow.setLineWidth(3); //Sphere sphere = new Sphere(30, 30, 0.2f); mark = new Geometry("BOOM!", arrow); //mark = new Geometry("BOOM!", sphere); Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mark_mat.getAdditionalRenderState().setLineWidth(3); mark_mat.setColor("Color", ColorRGBA.Red); mark.setMaterial(mark_mat); } diff --git a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java index eaf516fae..14f220374 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java +++ b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java @@ -40,12 +40,14 @@ import com.jme3.input.controls.KeyTrigger; import com.jme3.light.AmbientLight; import com.jme3.light.DirectionalLight; import com.jme3.material.Material; +import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.post.FilterPostProcessor; +import com.jme3.post.ssao.SSAOFilter; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; @@ -69,6 +71,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act private Geometry ground; private Material matGroundU; private Material matGroundL; + private AmbientLight al; public static void main(String[] args) { TestDirectionalLightShadow app = new TestDirectionalLightShadow(); @@ -99,7 +102,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act mat[0] = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); mat[1].setBoolean("UseMaterialColors", true); - mat[1].setColor("Ambient", ColorRGBA.White.mult(0.5f)); + mat[1].setColor("Ambient", ColorRGBA.White); mat[1].setColor("Diffuse", ColorRGBA.White.clone()); @@ -110,9 +113,14 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act TangentBinormalGenerator.generate(obj[1]); TangentBinormalGenerator.generate(obj[0]); + Spatial t = obj[0].clone(false); + t.setLocalScale(10f); + t.setMaterial(mat[1]); + rootNode.attachChild(t); + t.setLocalTranslation(0, 25, 0); for (int i = 0; i < 60; i++) { - Spatial t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false); + t = obj[FastMath.nextRandomInt(0, obj.length - 1)].clone(false); t.setLocalScale(FastMath.nextRandomFloat() * 10f); t.setMaterial(mat[FastMath.nextRandomInt(0, mat.length - 1)]); rootNode.attachChild(t); @@ -142,8 +150,8 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act rootNode.addLight(l); - AmbientLight al = new AmbientLight(); - al.setColor(ColorRGBA.White.mult(0.5f)); + al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(0.02f)); rootNode.addLight(al); Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", false); @@ -156,8 +164,11 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act @Override public void simpleInitApp() { // put the camera in a bad position - cam.setLocation(new Vector3f(65.25412f, 44.38738f, 9.087874f)); - cam.setRotation(new Quaternion(0.078139365f, 0.050241485f, -0.003942559f, 0.9956679f)); +// cam.setLocation(new Vector3f(65.25412f, 44.38738f, 9.087874f)); +// cam.setRotation(new Quaternion(0.078139365f, 0.050241485f, -0.003942559f, 0.9956679f)); + + cam.setLocation(new Vector3f(3.3720117f, 42.838284f, -83.43792f)); + cam.setRotation(new Quaternion(0.13833192f, -0.08969371f, 0.012581267f, 0.9862358f)); flyCam.setMoveSpeed(100); @@ -166,7 +177,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE, 3); dlsr.setLight(l); dlsr.setLambda(0.55f); - dlsr.setShadowIntensity(0.6f); + dlsr.setShadowIntensity(0.8f); dlsr.setEdgeFilteringMode(EdgeFilteringMode.Nearest); dlsr.displayDebug(); viewPort.addProcessor(dlsr); @@ -174,7 +185,7 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act dlsf = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE, 3); dlsf.setLight(l); dlsf.setLambda(0.55f); - dlsf.setShadowIntensity(0.6f); + dlsf.setShadowIntensity(0.8f); dlsf.setEdgeFilteringMode(EdgeFilteringMode.Nearest); dlsf.setEnabled(false); @@ -205,10 +216,11 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act inputManager.addMapping("fwd", new KeyTrigger(KeyInput.KEY_PGUP)); inputManager.addMapping("back", new KeyTrigger(KeyInput.KEY_PGDN)); inputManager.addMapping("pp", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addMapping("backShadows", new KeyTrigger(KeyInput.KEY_B)); inputManager.addListener(this, "lambdaUp", "lambdaDown", "ThicknessUp", "ThicknessDown", - "switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp", "stabilize", "distance"); + "switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp", "stabilize", "distance", "ShadowUp", "ShadowDown", "backShadows"); ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, dlsr, dlsf, guiNode, inputManager, viewPort); @@ -255,12 +267,19 @@ public class TestDirectionalLightShadow extends SimpleApplication implements Act dlsf.setLambda(dlsr.getLambda() - 0.01f); System.out.println("Lambda : " + dlsr.getLambda()); } - + if ((name.equals("ShadowUp") || name.equals("ShadowDown")) && keyPressed) { + al.setColor(ColorRGBA.White.mult((1 - dlsr.getShadowIntensity()) * 0.2f)); + } if (name.equals("debug") && keyPressed) { dlsr.displayFrustum(); } + if (name.equals("backShadows") && keyPressed) { + dlsr.setRenderBackFacesShadows(!dlsr.isRenderBackFacesShadows()); + dlsf.setRenderBackFacesShadows(!dlsf.isRenderBackFacesShadows()); + } + if (name.equals("stabilize") && keyPressed) { dlsr.setEnabledStabilization(!dlsr.isEnabledStabilization()); dlsf.setEnabledStabilization(!dlsf.isEnabledStabilization()); diff --git a/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java index a98ac03db..80079711d 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java +++ b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java @@ -32,7 +32,10 @@ package jme3test.light; import com.jme3.app.SimpleApplication; +import com.jme3.input.controls.ActionListener; +import com.jme3.light.AmbientLight; import com.jme3.light.PointLight; +import com.jme3.math.ColorRGBA; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.post.FilterPostProcessor; @@ -45,7 +48,7 @@ import com.jme3.shadow.EdgeFilteringMode; import com.jme3.shadow.PointLightShadowFilter; import com.jme3.shadow.PointLightShadowRenderer; -public class TestPointLightShadows extends SimpleApplication { +public class TestPointLightShadows extends SimpleApplication implements ActionListener{ public static final int SHADOWMAP_SIZE = 512; public static void main(String[] args) { @@ -55,13 +58,19 @@ public class TestPointLightShadows extends SimpleApplication { Node lightNode; PointLightShadowRenderer plsr; PointLightShadowFilter plsf; + AmbientLight al; @Override - public void simpleInitApp() { + public void simpleInitApp () { flyCam.setMoveSpeed(10); cam.setLocation(new Vector3f(0.040581334f, 1.7745866f, 6.155161f)); cam.setRotation(new Quaternion(4.3868728E-5f, 0.9999293f, -0.011230096f, 0.0039059948f)); + al = new AmbientLight(ColorRGBA.White.mult(0.02f)); + rootNode.addLight(al); + + + Node scene = (Node) assetManager.loadModel("Models/Test/CornellBox.j3o"); scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); @@ -89,6 +98,7 @@ public class TestPointLightShadows extends SimpleApplication { plsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4); plsr.setShadowZExtend(15); plsr.setShadowZFadeLength(5); + plsr.setShadowIntensity(0.9f); // plsr.setFlushQueues(false); //plsr.displayFrustum(); plsr.displayDebug(); @@ -99,18 +109,27 @@ public class TestPointLightShadows extends SimpleApplication { plsf.setLight((PointLight) scene.getLocalLightList().get(0)); plsf.setShadowZExtend(15); plsf.setShadowZFadeLength(5); + plsf.setShadowIntensity(0.8f); plsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4); plsf.setEnabled(false); FilterPostProcessor fpp = new FilterPostProcessor(assetManager); fpp.addFilter(plsf); viewPort.addProcessor(fpp); - + inputManager.addListener(this,"ShadowUp","ShadowDown"); ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, plsr, plsf, guiNode, inputManager, viewPort); + } @Override public void simpleUpdate(float tpf) { // lightNode.move(FastMath.cos(tpf) * 0.4f, 0, FastMath.sin(tpf) * 0.4f); } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if ((name.equals("ShadowUp") || name.equals("ShadowDown")) && isPressed) { + al.setColor(ColorRGBA.White.mult((1 - plsr.getShadowIntensity()) * 0.2f)); + } + } } \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java b/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java index 4c74b0664..55d5a3f87 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java +++ b/jme3-examples/src/main/java/jme3test/light/TestPssmShadow.java @@ -67,6 +67,8 @@ import com.jme3.texture.Texture; import com.jme3.texture.Texture.WrapMode; import com.jme3.util.SkyFactory; import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; public class TestPssmShadow extends SimpleApplication implements ActionListener { @@ -249,10 +251,20 @@ public class TestPssmShadow extends SimpleApplication implements ActionListener time = 0; } + @Override + public Object jmeClone() { + return null; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + } + @Override protected void controlRender(RenderManager rm, ViewPort vp) { } + @Override public Control cloneForSpatial(Spatial spatial) { return null; } diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java index 8d29ae498..7a6f35c37 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java @@ -58,7 +58,7 @@ public class TestSpotLight extends SimpleApplication { Geometry lightMdl; public void setupLighting(){ AmbientLight al=new AmbientLight(); - al.setColor(ColorRGBA.White.mult(0.8f)); + al.setColor(ColorRGBA.White.mult(0.02f)); rootNode.addLight(al); spot=new SpotLight(); diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java index fbf0e1b67..8df514fd3 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java @@ -65,7 +65,7 @@ public class TestSpotLightShadows extends SimpleApplication { public void setupLighting() { AmbientLight al = new AmbientLight(); - al.setColor(ColorRGBA.White.mult(0.3f)); + al.setColor(ColorRGBA.White.mult(0.02f)); rootNode.addLight(al); rootNode.setShadowMode(ShadowMode.CastAndReceive); @@ -132,11 +132,6 @@ public class TestSpotLightShadows extends SimpleApplication { }, "stop"); inputManager.addMapping("stop", new KeyTrigger(KeyInput.KEY_1)); - - MaterialDebugAppState s = new MaterialDebugAppState(); - s.registerBinding("Common/MatDefs/Shadow/PostShadow15.frag", rootNode); - s.registerBinding(new KeyTrigger(KeyInput.KEY_R), rootNode); - stateManager.attach(s); flyCam.setDragToRotate(true); } diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java index 4fb58602c..f656b1fd6 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java @@ -95,7 +95,7 @@ public class TestSpotLightTerrain extends SimpleApplication { rootNode.addLight(sl); AmbientLight ambLight = new AmbientLight(); - ambLight.setColor(new ColorRGBA(0.8f, 0.8f, 0.8f, 0.2f)); + ambLight.setColor(ColorRGBA.Black); rootNode.addLight(ambLight); cam.setLocation(new Vector3f(-41.219646f, -84.8363f, -171.67267f)); diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java index a786d767d..21817e7c8 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java @@ -72,7 +72,6 @@ public class TestTangentGenBadModels extends SimpleApplication { "debug tangents geom", TangentBinormalGenerator.genTbnLines(g.getMesh(), 0.2f) ); - debug.getMesh().setLineWidth(1); debug.setMaterial(debugMat); debug.setCullHint(Spatial.CullHint.Never); debug.setLocalTransform(g.getWorldTransform()); diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java b/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java new file mode 100644 index 000000000..9f8daf8e7 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java @@ -0,0 +1,100 @@ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.material.*; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; + +/** + * test + * + * @author normenhansen + */ +public class TestTangentSpace extends SimpleApplication { + + public static void main(String[] args) { + TestTangentSpace app = new TestTangentSpace(); + app.start(); + } + + private Node debugNode = new Node("debug"); + + @Override + public void simpleInitApp() { + renderManager.setSinglePassLightBatchSize(2); + renderManager.setPreferredLightMode(TechniqueDef.LightMode.SinglePass); + initView(); + + Spatial s = assetManager.loadModel("Models/Test/BasicCubeLow.obj"); + rootNode.attachChild(s); + + Material m = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + m.setTexture("NormalMap", assetManager.loadTexture("Models/Test/Normal_pixel.png")); + + Geometry g = (Geometry)s; + Geometry g2 = (Geometry) g.deepClone(); + g2.move(5, 0, 0); + g.getParent().attachChild(g2); + + g.setMaterial(m); + g2.setMaterial(m); + + //Regular tangent generation (left geom) + TangentBinormalGenerator.generate(g2.getMesh(), true); + + //MikkTSPace Tangent generation (right geom) + + MikktspaceTangentGenerator.generate(g); + + createDebugTangents(g2); + createDebugTangents(g); + + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("toggleDebug") && isPressed) { + if (debugNode.getParent() == null) { + rootNode.attachChild(debugNode); + } else { + debugNode.removeFromParent(); + } + } + } + }, "toggleDebug"); + + inputManager.addMapping("toggleDebug", new KeyTrigger(KeyInput.KEY_SPACE)); + + + DirectionalLight dl = new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal()); + rootNode.addLight(dl); + } + + private void initView() { + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + cam.setLocation(new Vector3f(8.569681f, 3.335546f, 5.4372444f)); + cam.setRotation(new Quaternion(-0.07608022f, 0.9086564f, -0.18992864f, -0.3639813f)); + flyCam.setMoveSpeed(10); + } + + private void createDebugTangents(Geometry geom) { + Geometry debug = new Geometry( + "Debug " + geom.getName(), + TangentBinormalGenerator.genTbnLines(geom.getMesh(), 0.8f) + ); + Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m"); + debug.setMaterial(debugMat); + debug.setCullHint(Spatial.CullHint.Never); + debug.getLocalTranslation().set(geom.getWorldTranslation()); + debugNode.attachChild(debug); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java index 6c4c45e2f..fc4731615 100644 --- a/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java @@ -50,10 +50,11 @@ public class TestDebugShapes extends SimpleApplication { app.start(); } - public Geometry putShape(Mesh shape, ColorRGBA color){ + public Geometry putShape(Mesh shape, ColorRGBA color, float lineWidth){ Geometry g = new Geometry("shape", shape); Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat.getAdditionalRenderState().setWireframe(true); + mat.getAdditionalRenderState().setLineWidth(lineWidth); mat.setColor("Color", color); g.setMaterial(mat); rootNode.attachChild(g); @@ -62,20 +63,19 @@ public class TestDebugShapes extends SimpleApplication { public void putArrow(Vector3f pos, Vector3f dir, ColorRGBA color){ Arrow arrow = new Arrow(dir); - arrow.setLineWidth(4); // make arrow thicker - putShape(arrow, color).setLocalTranslation(pos); + putShape(arrow, color, 4).setLocalTranslation(pos); } public void putBox(Vector3f pos, float size, ColorRGBA color){ - putShape(new WireBox(size, size, size), color).setLocalTranslation(pos); + putShape(new WireBox(size, size, size), color, 1).setLocalTranslation(pos); } public void putGrid(Vector3f pos, ColorRGBA color){ - putShape(new Grid(6, 6, 0.2f), color).center().move(pos); + putShape(new Grid(6, 6, 0.2f), color, 1).center().move(pos); } public void putSphere(Vector3f pos, ColorRGBA color){ - putShape(new WireSphere(1), color).setLocalTranslation(pos); + putShape(new WireSphere(1), color, 1).setLocalTranslation(pos); } @Override diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java index a36853913..2e8ecbfaf 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java @@ -41,6 +41,8 @@ import java.awt.Component; import java.awt.Dimension; import java.awt.event.ActionEvent; import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.*; import jme3test.network.TestChatServer.ChatMessage; @@ -115,6 +117,18 @@ public class TestChatClient extends JFrame { public static void main(String... args) throws Exception { + // Increate the logging level for networking... + System.out.println("Setting logging to max"); + Logger networkLog = Logger.getLogger("com.jme3.network"); + networkLog.setLevel(Level.FINEST); + + // And we have to tell JUL's handler also + // turn up logging in a very convoluted way + Logger rootLog = Logger.getLogger(""); + if( rootLog.getHandlers().length > 0 ) { + rootLog.getHandlers()[0].setLevel(Level.FINEST); + } + // Note: in JME 3.1 this is generally unnecessary as the server will // send a message with all server-registered classes. // TestChatServer.initializeClasses(); diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java index 4d8642881..8bdb740b9 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java @@ -31,6 +31,9 @@ */ package jme3test.network; +import java.util.logging.Level; +import java.util.logging.Logger; + import com.jme3.network.*; import com.jme3.network.serializing.Serializable; import com.jme3.network.serializing.Serializer; @@ -56,11 +59,15 @@ public class TestChatServer { private boolean isRunning; public TestChatServer() throws IOException { - initializeClasses(); // Use this to test the client/server name version check this.server = Network.createServer(NAME, VERSION, PORT, UDP_PORT); + // Initialize our own messages only after the server has been created. + // It registers some additional messages with the serializer by default + // that need to go before custom messages. + initializeClasses(); + ChatHandler handler = new ChatHandler(); server.addMessageListener(handler, ChatMessage.class); @@ -121,6 +128,18 @@ public class TestChatServer { } public static void main(String... args) throws Exception { + + // Increate the logging level for networking... + System.out.println("Setting logging to max"); + Logger networkLog = Logger.getLogger("com.jme3.network"); + networkLog.setLevel(Level.FINEST); + + // And we have to tell JUL's handler also + // turn up logging in a very convoluted way + Logger rootLog = Logger.getLogger(""); + if( rootLog.getHandlers().length > 0 ) { + rootLog.getHandlers()[0].setLevel(Level.FINEST); + } TestChatServer chatServer = new TestChatServer(); chatServer.start(); diff --git a/jme3-examples/src/main/java/jme3test/scene/TestLineWidthRenderState.java b/jme3-examples/src/main/java/jme3test/scene/TestLineWidthRenderState.java new file mode 100644 index 000000000..6bd629823 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/TestLineWidthRenderState.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.scene; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; + +public class TestLineWidthRenderState extends SimpleApplication { + + private Material mat; + + public static void main(String[] args){ + TestLineWidthRenderState app = new TestLineWidthRenderState(); + app.start(); + } + + + + @Override + public void simpleInitApp() { + setDisplayFps(false); + setDisplayStatView(false); + cam.setLocation(new Vector3f(5.5826545f, 3.6192513f, 8.016988f)); + cam.setRotation(new Quaternion(-0.04787097f, 0.9463123f, -0.16569641f, -0.27339742f)); + + Box b = new Box(1, 1, 1); + Geometry geom = new Geometry("Box", b); + mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Blue); + mat.getAdditionalRenderState().setWireframe(true); + mat.getAdditionalRenderState().setLineWidth(2); + geom.setMaterial(mat); + rootNode.attachChild(geom); + + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if(name.equals("up") && isPressed){ + mat.getAdditionalRenderState().setLineWidth(mat.getAdditionalRenderState().getLineWidth() + 1); + } + if(name.equals("down") && isPressed){ + mat.getAdditionalRenderState().setLineWidth(Math.max(mat.getAdditionalRenderState().getLineWidth() - 1, 1)); + } + } + }, "up", "down"); + inputManager.addMapping("up", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_J)); + } +} \ No newline at end of file diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index dabbe62a0..b26434cdd 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -14,4 +14,5 @@ sourceSets { dependencies { compile project(':jme3-core') + testCompile project(':jme3-desktop') } diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java new file mode 100644 index 000000000..0535c4582 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java @@ -0,0 +1,78 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.material.plugin.export.material; + +import com.jme3.export.JmeExporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.material.Material; +import com.jme3.material.MaterialDef; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; + +/** + * Saves a Material to a j3m file with proper formatting. + * + * usage is : + *
+ *     J3MExporter exporter = new J3MExporter();
+ *     exporter.save(material, myFile);
+ *     //or
+ *     exporter.save(material, myOutputStream);
+ * 
+ * + * @author tsr + * @author nehon (documentation and safety check) + */ +public class J3MExporter implements JmeExporter { + + private final J3MRootOutputCapsule rootCapsule; + + /** + * Create a J3MExporter + */ + public J3MExporter() { + rootCapsule = new J3MRootOutputCapsule(this); + } + + @Override + public void save(Savable object, OutputStream f) throws IOException { + + if (!(object instanceof Material)) { + throw new IllegalArgumentException("J3MExporter can only save com.jme3.material.Material class"); + } + + OutputStreamWriter out = new OutputStreamWriter(f, Charset.forName("UTF-8")); + + rootCapsule.clear(); + object.write(this); + rootCapsule.writeToStream(out); + + out.flush(); + } + + @Override + public void save(Savable object, File f) throws IOException { + try (FileOutputStream fos = new FileOutputStream(f)) { + save(object, fos); + } + } + + @Override + public OutputCapsule getCapsule(Savable object) { + if ((object instanceof Material) || (object instanceof MaterialDef)) { + return rootCapsule; + } + + return rootCapsule.getCapsule(object); + } + +} diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java new file mode 100644 index 000000000..7dd6a2a59 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MOutputCapsule.java @@ -0,0 +1,383 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.material.plugin.export.material; + +import com.jme3.asset.TextureKey; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.material.MatParam; +import com.jme3.material.MatParamTexture; +import com.jme3.math.*; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.util.IntMap; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author tsr + */ +public class J3MOutputCapsule implements OutputCapsule { + + private final HashMap parameters; + protected final J3MExporter exporter; + + public J3MOutputCapsule(J3MExporter exporter) { + this.exporter = exporter; + parameters = new HashMap<>(); + } + + public void writeToStream(OutputStreamWriter out) throws IOException { + for (String key : parameters.keySet()) { + out.write(" "); + writeParameter(out, key, parameters.get(key)); + out.write("\n"); + } + } + + protected void writeParameter(OutputStreamWriter out, String name, String value) throws IOException { + out.write(name); + out.write(" : "); + out.write(value); + } + + public void clear() { + parameters.clear(); + } + + protected void putParameter(String name, String value) { + parameters.put(name, value); + } + + @Override + public void write(boolean value, String name, boolean defVal) throws IOException { + if (value == defVal) { + return; + } + + putParameter(name, ((value) ? "On" : "Off")); + } + + @Override + public void writeStringSavableMap(Map map, String name, Map defVal) throws IOException { + for (String key : map.keySet()) { + Savable value = map.get(key); + if (defVal == null || !defVal.containsKey(key) || !defVal.get(key).equals(value)) { + putParameter(key, format(value)); + } + } + } + + protected String format(Savable value) { + if (value instanceof MatParamTexture) { + return formatMatParamTexture((MatParamTexture) value); + } + if (value instanceof MatParam) { + return formatMatParam((MatParam) value); + } + + throw new UnsupportedOperationException(value.getClass() + ": Not supported yet."); + } + + private String formatMatParam(MatParam param){ + VarType type = param.getVarType(); + Object val = param.getValue(); + switch (type) { + case Boolean: + case Float: + case Int: + return val.toString(); + case Vector2: + Vector2f v2 = (Vector2f) val; + return v2.getX() + " " + v2.getY(); + case Vector3: + Vector3f v3 = (Vector3f) val; + return v3.getX() + " " + v3.getY() + " " + v3.getZ(); + case Vector4: + // can be either ColorRGBA, Vector4f or Quaternion + if (val instanceof Vector4f) { + Vector4f v4 = (Vector4f) val; + return v4.getX() + " " + v4.getY() + " " + + v4.getZ() + " " + v4.getW(); + } else if (val instanceof ColorRGBA) { + ColorRGBA color = (ColorRGBA) val; + return color.getRed() + " " + color.getGreen() + " " + + color.getBlue() + " " + color.getAlpha(); + } else if (val instanceof Quaternion) { + Quaternion quat = (Quaternion) val; + return quat.getX() + " " + quat.getY() + " " + + quat.getZ() + " " + quat.getW(); + } else { + throw new UnsupportedOperationException("Unexpected Vector4 type: " + val); + } + + default: + return null; // parameter type not supported in J3M + } + } + + protected static String formatMatParamTexture(MatParamTexture param) { + StringBuilder ret = new StringBuilder(); + Texture tex = (Texture) param.getValue(); + TextureKey key; + if (tex != null) { + key = (TextureKey) tex.getKey(); + + if (key != null && key.isFlipY()) { + ret.append("Flip "); + } + + ret.append(formatWrapMode(tex, Texture.WrapAxis.S)); + ret.append(formatWrapMode(tex, Texture.WrapAxis.T)); + ret.append(formatWrapMode(tex, Texture.WrapAxis.R)); + + //Min and Mag filter + Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps; + if (tex.getImage().hasMipmaps() || (key != null && key.isGenerateMips())) { + def = Texture.MinFilter.Trilinear; + } + if (tex.getMinFilter() != def) { + ret.append("Min").append(tex.getMinFilter().name()).append(" "); + } + + if (tex.getMagFilter() != Texture.MagFilter.Bilinear) { + ret.append("Mag").append(tex.getMagFilter().name()).append(" "); + } + + ret.append("\"").append(key.getName()).append("\""); + } + + return ret.toString(); + } + + protected static String formatWrapMode(Texture texVal, Texture.WrapAxis axis) { + WrapMode mode; + try { + mode = texVal.getWrap(axis); + } catch (IllegalArgumentException e) { + //this axis doesn't exist on the texture + return ""; + } + if (mode != WrapMode.EdgeClamp) { + return "Wrap" + mode.name() + "_" + axis.name() + " "; + } + return ""; + } + + @Override + public void write(Enum value, String name, Enum defVal) throws IOException { + if (value == defVal) { + return; + } + + putParameter(name, value.toString()); + } + + @Override + public void write(float value, String name, float defVal) throws IOException { + if (value == defVal) { + return; + } + + putParameter(name, Float.toString(value)); + } + + @Override + public void write(float[] value, String name, float[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(float[][] value, String name, float[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(double value, String name, double defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(double[] value, String name, double[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(double[][] value, String name, double[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(long value, String name, long defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(long[] value, String name, long[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(long[][] value, String name, long[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(short value, String name, short defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(short[] value, String name, short[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(short[][] value, String name, short[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(boolean[] value, String name, boolean[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(boolean[][] value, String name, boolean[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(String value, String name, String defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(String[] value, String name, String[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(String[][] value, String name, String[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(BitSet value, String name, BitSet defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(Savable object, String name, Savable defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(Savable[] objects, String name, Savable[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(Savable[][] objects, String name, Savable[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeSavableArrayListArray(ArrayList[] array, String name, ArrayList[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeSavableArrayListArray2D(ArrayList[][] array, String name, ArrayList[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeFloatBufferArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeByteBufferArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeSavableMap(Map map, String name, Map defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void writeIntSavableMap(IntMap map, String name, IntMap defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(byte value, String name, byte defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(byte[] value, String name, byte[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(byte[][] value, String name, byte[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(int value, String name, int defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(int[] value, String name, int[] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void write(int[][] value, String name, int[][] defVal) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + +} diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java new file mode 100644 index 000000000..02c8df02c --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java @@ -0,0 +1,82 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.material.plugin.export.material; + +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.HashMap; + +/** + * + * @author tsr + */ +public class J3MRenderStateOutputCapsule extends J3MOutputCapsule { + protected final static HashMap NAME_MAP; + protected String offsetUnit; + + static { + NAME_MAP = new HashMap<>(); + NAME_MAP.put( "wireframe", "Wireframe"); + NAME_MAP.put( "cullMode", "FaceCull"); + NAME_MAP.put( "depthWrite", "DepthWrite"); + NAME_MAP.put( "depthTest", "DepthTest"); + NAME_MAP.put( "blendMode", "Blend"); + NAME_MAP.put( "alphaFallOff", "AlphaTestFalloff"); + NAME_MAP.put( "offsetFactor", "PolyOffset"); + NAME_MAP.put( "colorWrite", "ColorWrite"); + NAME_MAP.put( "pointSprite", "PointSprite"); + NAME_MAP.put( "depthFunc", "DepthFunc"); + NAME_MAP.put( "alphaFunc", "AlphaFunc"); + NAME_MAP.put( "lineWidth", "LineWidth"); + } + public J3MRenderStateOutputCapsule(J3MExporter exporter) { + super(exporter); + } + + public OutputCapsule getCapsule(Savable object) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public void clear() { + super.clear(); + offsetUnit = ""; + } + + @Override + public void writeToStream(OutputStreamWriter out) throws IOException { + out.write(" AdditionalRenderState {\n"); + super.writeToStream(out); + out.write(" }\n"); + } + + @Override + protected void writeParameter(OutputStreamWriter out, String name, String value) throws IOException { + out.write(name); + out.write(" "); + out.write(value); + + if( "PolyOffset".equals(name) ) { + out.write(" "); + out.write(offsetUnit); + } + } + + @Override + protected void putParameter(String name, String value ) { + if( "offsetUnits".equals(name) ) { + offsetUnit = value; + return; + } + + if( !NAME_MAP.containsKey(name) ) + return; + + super.putParameter(NAME_MAP.get(name), value); + } +} diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java new file mode 100644 index 000000000..40130b705 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java @@ -0,0 +1,99 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.jme3.material.plugin.export.material; + +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.util.HashMap; + +/** + * @author tsr + */ +public class J3MRootOutputCapsule extends J3MOutputCapsule { + + private final HashMap outCapsules; + private String name; + private String materialDefinition; + private Boolean isTransparent; + + public J3MRootOutputCapsule(J3MExporter exporter) { + super(exporter); + outCapsules = new HashMap<>(); + } + + @Override + public void clear() { + super.clear(); + isTransparent = null; + name = ""; + materialDefinition = ""; + outCapsules.clear(); + + } + + public OutputCapsule getCapsule(Savable object) { + if (!outCapsules.containsKey(object)) { + outCapsules.put(object, new J3MRenderStateOutputCapsule(exporter)); + } + + return outCapsules.get(object); + } + + @Override + public void writeToStream(OutputStreamWriter out) throws IOException { + out.write("Material " + name + " : " + materialDefinition + " {\n\n"); + if (isTransparent != null) + out.write(" Transparent " + ((isTransparent) ? "On" : "Off") + "\n\n"); + + out.write(" MaterialParameters {\n"); + super.writeToStream(out); + out.write(" }\n\n"); + + for (J3MOutputCapsule c : outCapsules.values()) { + c.writeToStream(out); + } + out.write("}\n"); + } + + @Override + public void write(String value, String name, String defVal) throws IOException { + switch (name) { + case "material_def": + materialDefinition = value; + break; + case "name": + this.name = value; + break; + default: + throw new UnsupportedOperationException(name + " string material parameter not supported yet"); + } + } + + @Override + public void write(boolean value, String name, boolean defVal) throws IOException { + if( value == defVal) + return; + + switch (name) { + case "is_transparent": + isTransparent = value; + break; + default: + throw new UnsupportedOperationException(name + " boolean material parameter not supported yet"); + } + } + + @Override + public void write(Savable object, String name, Savable defVal) throws IOException { + if(object != null && !object.equals(defVal)) { + object.write(exporter); + } + } + +} diff --git a/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java b/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java new file mode 100644 index 000000000..be8fc3573 --- /dev/null +++ b/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2009-2016 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material.plugin; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.material.plugin.export.material.J3MExporter; +import com.jme3.material.plugins.J3MLoader; +import com.jme3.math.ColorRGBA; +import com.jme3.system.JmeSystem; +import com.jme3.texture.Texture; +import org.junit.Before; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertTrue; + +public class TestMaterialWrite { + + private AssetManager assetManager; + + @Before + public void init() { + assetManager = JmeSystem.newAssetManager( + TestMaterialWrite.class.getResource("/com/jme3/asset/Desktop.cfg")); + + + } + + + @Test + public void testWriteMat() throws Exception { + + Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); + + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Diffuse", ColorRGBA.White); + mat.setColor("Ambient", ColorRGBA.DarkGray); + mat.setFloat("AlphaDiscardThreshold", 0.5f); + + mat.setFloat("Shininess", 2.5f); + + Texture tex = assetManager.loadTexture("Common/Textures/MissingTexture.png"); + tex.setMagFilter(Texture.MagFilter.Nearest); + tex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + tex.setWrap(Texture.WrapAxis.S, Texture.WrapMode.Repeat); + tex.setWrap(Texture.WrapAxis.T, Texture.WrapMode.MirroredRepeat); + + mat.setTexture("DiffuseMap", tex); + mat.getAdditionalRenderState().setDepthWrite(false); + mat.getAdditionalRenderState().setDepthTest(false); + mat.getAdditionalRenderState().setLineWidth(5); + mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + + final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + + J3MExporter exporter = new J3MExporter(); + try { + exporter.save(mat, stream); + } catch (IOException e) { + e.printStackTrace(); + } + + System.err.println(stream.toString()); + + J3MLoader loader = new J3MLoader(); + AssetInfo info = new AssetInfo(assetManager, new AssetKey("test")) { + @Override + public InputStream openStream() { + return new ByteArrayInputStream(stream.toByteArray()); + } + }; + Material mat2 = (Material)loader.load(info); + + assertTrue(mat.contentEquals(mat2)); + } + +} diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java index 4c41e739e..c43c491ec 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/NormalRecalcControl.java @@ -40,6 +40,8 @@ import com.jme3.renderer.ViewPort; import com.jme3.scene.Spatial; import com.jme3.scene.control.AbstractControl; import com.jme3.scene.control.Control; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; @@ -67,6 +69,14 @@ public class NormalRecalcControl extends AbstractControl { } + @Override + public Object jmeClone() { + NormalRecalcControl control = (NormalRecalcControl)super.jmeClone(); + control.setEnabled(true); + return control; + } + + @Override public Control cloneForSpatial(Spatial spatial) { NormalRecalcControl control = new NormalRecalcControl(terrain); control.setSpatial(spatial); diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java index 8ad2425ad..f52b1ca89 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java @@ -46,6 +46,8 @@ import com.jme3.scene.control.Control; import com.jme3.terrain.Terrain; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.geomipmap.lodcalc.LodCalculator; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -298,9 +300,32 @@ public class TerrainLodControl extends AbstractControl { + + @Override + public Object jmeClone() { + if (spatial instanceof Terrain) { + TerrainLodControl cloned = new TerrainLodControl((Terrain) spatial, cameras); + cloned.setLodCalculator(lodCalculator.clone()); + cloned.spatial = spatial; + return cloned; + } + return null; + } + + @Override + public void cloneFields( Cloner cloner, Object original ) { + this.lodCalculator = cloner.clone(lodCalculator); + + try { + // Not deep clone of the cameras themselves + this.cameras = cloner.javaClone(cameras); + } catch( CloneNotSupportedException e ) { + throw new RuntimeException("Error cloning", e); + } + } - + @Override public Control cloneForSpatial(Spatial spatial) { if (spatial instanceof Terrain) { List cameraClone = new ArrayList(); diff --git a/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m index 91967d5c4..5d732d0c0 100644 --- a/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m +++ b/jme3-testdata/src/main/resources/Models/Sign Post/Sign Post.j3m @@ -1,11 +1,11 @@ Material Signpost : Common/MatDefs/Light/Lighting.j3md { MaterialParameters { Shininess: 4.0 - DiffuseMap: Models/Sign Post/Sign Post.jpg - NormalMap: Models/Sign Post/Sign Post_normal.jpg - SpecularMap: Models/Sign Post/Sign Post_specular.jpg + DiffuseMap: "Models/Sign Post/Sign Post.jpg" + NormalMap: "Models/Sign Post/Sign Post_normal.jpg" + SpecularMap: "Models/Sign Post/Sign Post_specular.jpg" UseMaterialColors : true - Ambient : 0.5 0.5 0.5 1.0 + Ambient : 1.0 1.0 1.0 1.0 Diffuse : 1.0 1.0 1.0 1.0 Specular : 1.0 1.0 1.0 1.0 } diff --git a/jme3-testdata/src/main/resources/Models/Test/BasicCubeLow.obj b/jme3-testdata/src/main/resources/Models/Test/BasicCubeLow.obj new file mode 100644 index 000000000..fb00257de --- /dev/null +++ b/jme3-testdata/src/main/resources/Models/Test/BasicCubeLow.obj @@ -0,0 +1,46 @@ +# Blender v2.76 (sub 0) OBJ File: 'BasicCube.blend' +# www.blender.org +o Cube +v 1.000000 -1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +v 1.000000 1.000000 -0.999999 +v 0.999999 1.000000 1.000001 +v -1.000000 1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +vt 0.500000 0.250043 +vt 0.749957 0.250043 +vt 0.749957 0.500000 +vt 0.000087 0.500000 +vt 0.000087 0.250043 +vt 0.250043 0.250043 +vt 0.250043 0.500000 +vt 0.250043 0.000087 +vt 0.500000 0.000087 +vt 0.999913 0.250043 +vt 0.999913 0.500000 +vt 0.500000 0.500000 +vt 0.500000 0.749957 +vt 0.250044 0.749957 +vn 0.577300 -0.577300 0.577300 +vn -0.577300 -0.577300 0.577300 +vn -0.577300 -0.577300 -0.577300 +vn -0.577300 0.577300 -0.577300 +vn -0.577300 0.577300 0.577300 +vn 0.577300 0.577300 0.577300 +vn 0.577300 0.577300 -0.577300 +vn 0.577300 -0.577300 -0.577300 +s 1 +f 2/1/1 3/2/2 4/3/3 +f 8/4/4 7/5/5 6/6/6 +f 5/7/7 6/6/6 2/1/1 +f 6/6/6 7/8/5 3/9/2 +f 3/2/2 7/10/5 8/11/4 +f 1/12/8 4/13/3 8/14/4 +f 1/12/8 2/1/1 4/3/3 +f 5/7/7 8/4/4 6/6/6 +f 1/12/8 5/7/7 2/1/1 +f 2/1/1 6/6/6 3/9/2 +f 4/3/3 3/2/2 8/11/4 +f 5/7/7 1/12/8 8/14/4 diff --git a/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o b/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o index 1d56eefa9..d67a2263d 100644 Binary files a/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o and b/jme3-testdata/src/main/resources/Models/Test/CornellBox.j3o differ diff --git a/jme3-testdata/src/main/resources/Models/Test/Normal_pixel.png b/jme3-testdata/src/main/resources/Models/Test/Normal_pixel.png new file mode 100644 index 000000000..d9adc6971 Binary files /dev/null and b/jme3-testdata/src/main/resources/Models/Test/Normal_pixel.png differ