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/gradle.properties b/gradle.properties index f4c74445a..e18680a7a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,6 @@ jmeVersionTagID = 0 buildJavaDoc = true # specify if SDK and Native libraries get built -buildSdkProject = true buildNativeProjects = false buildAndroidExamples = false 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/Face.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java index a746df180..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()); 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/Bone.java b/jme3-core/src/main/java/com/jme3/animation/Bone.java index 29c0f38e9..fcbacb337 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Bone.java +++ b/jme3-core/src/main/java/com/jme3/animation/Bone.java @@ -139,11 +139,7 @@ public final class Bone implements Savable { /** * Special-purpose copy constructor. *

- * Only copies the name and bind pose from the original. - *

- * WARNING: Local bind pose and world inverse bind pose transforms shallow - * copied. Modifying that data on the original bone will cause it to - * be recomputed on any cloned bones. + * Only copies the name, user control state and bind pose transforms from the original. *

* The rest of the data is NOT copied, as it will be * generated automatically when the bone is animated. @@ -155,13 +151,13 @@ public final class Bone implements Savable { userControl = source.userControl; - bindPos = source.bindPos; - bindRot = source.bindRot; - bindScale = source.bindScale; + bindPos = source.bindPos.clone(); + bindRot = source.bindRot.clone(); + bindScale = source.bindScale.clone(); - modelBindInversePos = source.modelBindInversePos; - modelBindInverseRot = source.modelBindInverseRot; - modelBindInverseScale = source.modelBindInverseScale; + modelBindInversePos = source.modelBindInversePos.clone(); + modelBindInverseRot = source.modelBindInverseRot.clone(); + modelBindInverseScale = source.modelBindInverseScale.clone(); // parent and children will be assigned manually.. } 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..9fb7670fe 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; @@ -115,10 +117,21 @@ public class StatsView extends Node implements Control { //statistics.clearFrame(); } + @Override public Control cloneForSpatial(Spatial spatial) { return (Control) spatial; } + @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) { } 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/asset/AssetKey.java b/jme3-core/src/main/java/com/jme3/asset/AssetKey.java index a266bd74c..4066d7e14 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetKey.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetKey.java @@ -156,7 +156,7 @@ public class AssetKey implements Savable, Cloneable { list.removeLast(); } else { list.add(".."); - Logger.getLogger(AssetKey.class.getName()).log(Level.SEVERE, "Asset path is outside assetmanager root"); + Logger.getLogger(AssetKey.class.getName()).log(Level.SEVERE, "Asset path \"{0}\" is outside assetmanager root", path); } } else { list.add(string); diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java index 8664af6c7..2c5cc5b98 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2012, 2016 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,7 @@ package com.jme3.audio; import com.jme3.asset.AssetManager; import com.jme3.asset.AssetNotFoundException; +import com.jme3.audio.AudioData.DataType; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; @@ -127,6 +128,17 @@ public class AudioNode extends Node implements AudioSource { public AudioNode(AudioData audioData, AudioKey audioKey) { setAudioData(audioData, audioKey); } + + /** + * Creates a new AudioNode with the given audio file. + * @param assetManager The asset manager to use to load the audio file + * @param name The filename of the audio file + * @param type The type. If {@link com.jme3.audio.AudioData.DataType}.Stream, the audio will be streamed gradually from disk, + * otherwise it will be buffered ({@link com.jme3.audio.AudioData.DataType}.Buffer) + */ + public AudioNode(AssetManager assetManager, String name, DataType type) { + this(assetManager, name, type == DataType.Stream, true); + } /** * Creates a new AudioNode with the given audio file. @@ -139,6 +151,8 @@ public class AudioNode extends Node implements AudioSource { * the stream cache is used. When enabled, the audio stream will * be read entirely but not decoded, allowing features such as * seeking, looping and determining duration. + * + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead */ public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) { this.audioKey = new AudioKey(name, stream, streamCache); @@ -152,9 +166,11 @@ public class AudioNode extends Node implements AudioSource { * @param name The filename of the audio file * @param stream If true, the audio will be streamed gradually from disk, * otherwise, it will be buffered. + * + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead */ public AudioNode(AssetManager assetManager, String name, boolean stream) { - this(assetManager, name, stream, false); + this(assetManager, name, stream, true); // Always streamCached } /** @@ -167,7 +183,7 @@ public class AudioNode extends Node implements AudioSource { * @deprecated AudioRenderer parameter is ignored. */ public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String name) { - this(assetManager, name, false); + this(assetManager, name, DataType.Buffer); } /** @@ -175,9 +191,10 @@ public class AudioNode extends Node implements AudioSource { * * @param assetManager The asset manager to use to load the audio file * @param name The filename of the audio file + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType) } instead */ public AudioNode(AssetManager assetManager, String name) { - this(assetManager, name, false); + this(assetManager, name, DataType.Buffer); } protected AudioRenderer getRenderer() { @@ -310,6 +327,19 @@ public class AudioNode extends Node implements AudioSource { this.status = status; } + /** + * Get the Type of the underlying AudioData to see if it's streamed or buffered. + * This is a shortcut to getAudioData().getType() + * Warning: Can return null! + * @return The {@link com.jme3.audio.AudioData.DataType} of the audio node. + */ + public DataType getType() { + if (data == null) + return null; + else + return data.getDataType(); + } + /** * @return True if the audio will keep looping after it is done playing, * otherwise, false. 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..dbb9b494c 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; @@ -274,6 +276,7 @@ public class MotionEvent extends AbstractCinematicEvent implements Control { * @param spatial * @return */ + @Override public Control cloneForSpatial(Spatial spatial) { MotionEvent control = new MotionEvent(spatial, path); control.playState = playState; @@ -291,6 +294,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/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/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/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index c3a0ab4bb..e352394bd 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -31,14 +31,6 @@ */ package com.jme3.scene; -import com.jme3.export.*; -import com.jme3.material.Material; -import com.jme3.math.Matrix4f; -import com.jme3.math.Vector3f; -import com.jme3.scene.mesh.IndexBuffer; -import com.jme3.util.SafeArrayList; -import com.jme3.util.TempVars; -import java.io.IOException; import java.nio.Buffer; import java.nio.FloatBuffer; import java.util.ArrayList; @@ -48,13 +40,22 @@ import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; +import com.jme3.material.Material; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.SafeArrayList; +import com.jme3.util.TempVars; + /** * BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph. * There is one geometry per different material in the sub tree. * The geometries are directly attached to the node in the scene graph. * Usage is like any other node except you have to call the {@link #batch()} method once all the geometries have been attached to the sub scene graph and their material set * (see todo more automagic for further enhancements) - * All the geometries that have been batched are set to {@link CullHint#Always} to not render them. + * All the geometries that have been batched are set to not be rendered - {@link CullHint} is left intact. * The sub geometries can be transformed as usual, their transforms are used to update the mesh of the geometryBatch. * 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. @@ -72,7 +73,7 @@ public class BatchNode extends GeometryGroupNode { */ protected SafeArrayList batches = new SafeArrayList(Batch.class); /** - * a map storing he batches by geometry to quickly acces the batch when updating + * a map for storing the batches by geometry to quickly access the batch when updating */ protected Map batchesByGeom = new HashMap(); /** @@ -115,10 +116,9 @@ public class BatchNode extends GeometryGroupNode { } @Override - public void onGeoemtryUnassociated(Geometry geom) { + public void onGeometryUnassociated(Geometry geom) { setNeedsFullRebatch(true); } - protected Matrix4f getTransformMatrix(Geometry g){ return g.cachedWorldMat; @@ -166,7 +166,7 @@ public class BatchNode extends GeometryGroupNode { */ public void batch() { doBatch(); - //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice + //we set the batch geometries to ignore transforms to avoid transforms of parent nodes to be applied twice for (Batch batch : batches.getArray()) { batch.geometry.setIgnoreTransform(true); batch.geometry.setUserData(UserData.JME_PHYSICSIGNORE, true); @@ -174,10 +174,10 @@ public class BatchNode extends GeometryGroupNode { } protected void doBatch() { - Map> matMap = new HashMap>(); + Map> matMap = new HashMap>(); int nbGeoms = 0; - gatherGeomerties(matMap, this, needsFullRebatch); + gatherGeometries(matMap, this, needsFullRebatch); if (needsFullRebatch) { for (Batch batch : batches.getArray()) { batch.geometry.removeFromParent(); @@ -221,7 +221,7 @@ public class BatchNode extends GeometryGroupNode { batch.geometry.setMesh(m); batch.geometry.getMesh().updateCounts(); - batch.geometry.updateModelBound(); + batch.geometry.updateModelBound(); batches.add(batch); } if (batches.size() > 0) { @@ -271,7 +271,7 @@ public class BatchNode extends GeometryGroupNode { } - private void gatherGeomerties(Map> map, Spatial n, boolean rebatch) { + private void gatherGeometries(Map> map, Spatial n, boolean rebatch) { if (n instanceof Geometry) { @@ -304,7 +304,7 @@ public class BatchNode extends GeometryGroupNode { if (child instanceof BatchNode) { continue; } - gatherGeomerties(map, child, rebatch); + gatherGeometries(map, child, rebatch); } } @@ -319,7 +319,7 @@ public class BatchNode extends GeometryGroupNode { return null; } - private boolean isBatch(Spatial s) { + public final boolean isBatch(Spatial s) { for (Batch batch : batches.getArray()) { if (batch.geometry == s) { return true; @@ -336,9 +336,6 @@ public class BatchNode extends GeometryGroupNode { */ @Override public void setMaterial(Material material) { -// for (Batch batch : batches.values()) { -// batch.geometry.setMaterial(material); -// } throw new UnsupportedOperationException("Unsupported for now, please set the material on the geoms before batching"); } @@ -356,74 +353,7 @@ public class BatchNode extends GeometryGroupNode { Batch b = batches.iterator().next(); return b.geometry.getMaterial(); } - return null;//material; - } - -// /** -// * Sets the material to the a specific batch of this BatchNode -// * -// * -// * @param material the material to use for this geometry -// */ -// public void setMaterial(Material material,int batchIndex) { -// if (!batches.isEmpty()) { -// -// } -// -// } -// -// /** -// * 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) -// */ -// public Material getMaterial(int batchIndex) { -// if (!batches.isEmpty()) { -// Batch b = batches.get(batches.keySet().iterator().next()); -// return b.geometry.getMaterial(); -// } -// return null;//material; -// } - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule oc = ex.getCapsule(this); -// -// if (material != null) { -// oc.write(material.getAssetName(), "materialName", null); -// } -// oc.write(material, "material", null); - - } - - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule ic = im.getCapsule(this); - - -// material = null; -// String matName = ic.readString("materialName", null); -// if (matName != null) { -// // Material name is set, -// // Attempt to load material via J3M -// try { -// material = im.getAssetManager().loadMaterial(matName); -// } catch (AssetNotFoundException ex) { -// // Cannot find J3M file. -// logger.log(Level.FINE, "Could not load J3M file {0} for Geometry.", -// matName); -// } -// } -// // If material is NULL, try to load it from the geometry -// if (material == null) { -// material = (Material) ic.readSavable("material", null); -// } - + return null; } /** @@ -494,7 +424,7 @@ public class BatchNode extends GeometryGroupNode { if (mode != null && mode != listMode) { throw new UnsupportedOperationException("Cannot combine different" + " primitive types: " + mode + " != " + listMode); - } + } mode = listMode; if (mode == Mesh.Mode.Lines) { if (lineWidth != 1f && listLineWidth != lineWidth) { @@ -510,8 +440,7 @@ public class BatchNode extends GeometryGroupNode { outMesh.setMode(mode); outMesh.setLineWidth(lineWidth); if (totalVerts >= 65536) { - // make sure we create an UnsignedInt buffer so - // we can fit all of the meshes + // make sure we create an UnsignedInt buffer so we can fit all of the meshes formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt; } else { formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort; @@ -733,7 +662,6 @@ public class BatchNode extends GeometryGroupNode { } protected class Batch { - /** * update the batchesByGeom map for this batch with the given List of geometries * @param list @@ -745,7 +673,11 @@ public class BatchNode extends GeometryGroupNode { } } } - Geometry geometry; + Geometry geometry; + + public final Geometry getGeometry() { + return geometry; + } } protected void setNeedsFullRebatch(boolean needsFullRebatch) { @@ -771,4 +703,15 @@ public class BatchNode extends GeometryGroupNode { } return clone; } + + @Override + public int collideWith(Collidable other, CollisionResults results) { + int total = 0; + for (Spatial child : children.getArray()){ + if (!isBatch(child)) { + total += child.collideWith(other, results); + } + } + return total; + } } 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 89f37ec9d..4c394101a 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -344,7 +344,7 @@ public class Geometry extends Spatial { if (groupNode != null) { // Once the geometry is removed // from the parent, the group node needs to be updated. - groupNode.onGeoemtryUnassociated(this); + groupNode.onGeometryUnassociated(this); groupNode = null; // change the default to -1 to make error detection easier diff --git a/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java b/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java index 2665f768e..3a99759d1 100644 --- a/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java @@ -83,5 +83,5 @@ public abstract class GeometryGroupNode extends Node { * * @param geom The Geometry which is being unassociated. */ - public abstract void onGeoemtryUnassociated(Geometry geom); + public abstract void onGeometryUnassociated(Geometry geom); } 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/InstancedNode.java b/jme3-core/src/main/java/com/jme3/scene/instancing/InstancedNode.java index 554b8606a..45dd85417 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,6 +44,8 @@ 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; @@ -106,7 +108,7 @@ public class InstancedNode extends GeometryGroupNode { } } - private static class InstancedNodeControl implements Control { + private static class InstancedNodeControl implements Control, JmeCloneable { private InstancedNode node; @@ -124,6 +126,20 @@ public class InstancedNode extends GeometryGroupNode { // 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){ } @@ -329,7 +345,7 @@ public class InstancedNode extends GeometryGroupNode { } @Override - public void onGeoemtryUnassociated(Geometry geom) { + public void onGeometryUnassociated(Geometry geom) { removeFromInstancedGeometry(geom); } } diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java b/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java index 1d215eb6b..1e0b1cae2 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java @@ -211,7 +211,7 @@ public class Cylinder extends Mesh { */ public void updateGeometry(int axisSamples, int radialSamples, float radius, float radius2, float height, boolean closed, boolean inverted) { - this.axisSamples = axisSamples + (closed ? 2 : 0); + this.axisSamples = axisSamples; this.radialSamples = radialSamples; this.radius = radius; this.radius2 = radius2; @@ -222,6 +222,7 @@ public class Cylinder extends Mesh { // VertexBuffer pvb = getBuffer(Type.Position); // VertexBuffer nvb = getBuffer(Type.Normal); // VertexBuffer tvb = getBuffer(Type.TexCoord); + axisSamples += (closed ? 2 : 0); // Vertices int vertCount = axisSamples * (radialSamples + 1) + (closed ? 2 : 0); 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/BufferUtils.java b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java index 24af81bf6..feb3ff8cd 100644 --- a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java +++ b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java @@ -51,7 +51,6 @@ import java.nio.IntBuffer; import java.nio.LongBuffer; import java.nio.ShortBuffer; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -1268,7 +1267,6 @@ public final class BufferUtils { System.out.println(store.toString()); } } - private static final AtomicBoolean loadedMethods = new AtomicBoolean(false); private static Method cleanerMethod = null; private static Method cleanMethod = null; private static Method viewedBufferMethod = null; @@ -1288,31 +1286,23 @@ public final class BufferUtils { } } - private static void loadCleanerMethods() { - // If its already true, exit, if not, set it to true. - if (BufferUtils.loadedMethods.getAndSet(true)) { - return; + static { + // Oracle JRE / OpenJDK + cleanerMethod = loadMethod("sun.nio.ch.DirectBuffer", "cleaner"); + cleanMethod = loadMethod("sun.misc.Cleaner", "clean"); + viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "viewedBuffer"); + if (viewedBufferMethod == null) { + // They changed the name in Java 7 (???) + viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "attachment"); } - // This could potentially be called many times if used from multiple - // threads - synchronized (BufferUtils.loadedMethods) { - // Oracle JRE / OpenJDK - cleanerMethod = loadMethod("sun.nio.ch.DirectBuffer", "cleaner"); - cleanMethod = loadMethod("sun.misc.Cleaner", "clean"); - viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "viewedBuffer"); - if (viewedBufferMethod == null) { - // They changed the name in Java 7 (???) - viewedBufferMethod = loadMethod("sun.nio.ch.DirectBuffer", "attachment"); - } - // Apache Harmony - ByteBuffer bb = BufferUtils.createByteBuffer(1); - Class clazz = bb.getClass(); - try { - freeMethod = clazz.getMethod("free"); - } catch (NoSuchMethodException ex) { - } catch (SecurityException ex) { - } + // Apache Harmony + ByteBuffer bb = BufferUtils.createByteBuffer(1); + Class clazz = bb.getClass(); + try { + freeMethod = clazz.getMethod("free"); + } catch (NoSuchMethodException ex) { + } catch (SecurityException ex) { } } @@ -1333,8 +1323,6 @@ public final class BufferUtils { return; } - BufferUtils.loadCleanerMethods(); - try { if (freeMethod != null) { freeMethod.invoke(toBeDestroyed); 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/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/FlatHeightmap.java b/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java similarity index 69% rename from sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/FlatHeightmap.java rename to jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java index 8f9969ee1..ac3dce6ee 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/FlatHeightmap.java +++ b/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java @@ -1,63 +1,58 @@ -/* - * Copyright (c) 2009-2010 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.gde.terraineditor; - -import com.jme3.terrain.heightmap.AbstractHeightMap; - -/** - * Very simple heightmap class that is a heightmap of floats that is size*size - * in size, and has height values of zero. - * - * @author bowens - */ -public class FlatHeightmap extends AbstractHeightMap { - - private int size; - private float[] heightmapData; - - public FlatHeightmap(int size) { - this.size = size; - } - - @Override - public boolean load() { - heightmapData = new float[size*size]; - return true; - } - - @Override - public float[] getHeightMap() { - return heightmapData; - } - -} +/* + * 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/sdk/jme3-core/src/com/jme3/gde/core/util/Beans.java b/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java similarity index 52% rename from sdk/jme3-core/src/com/jme3/gde/core/util/Beans.java rename to jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java index 938e5fae9..a1f269d67 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/util/Beans.java +++ b/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java @@ -1,55 +1,70 @@ -/* - * Copyright (c) 2003-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 com.jme3.gde.core.util; - -import java.lang.reflect.InvocationTargetException; -import org.apache.commons.beanutils.BeanUtils; -import org.openide.util.Exceptions; - -/** - * - * @author normenhansen - */ -public class Beans { - - public static boolean copyProperties(Object dest, Object src) { - try { - BeanUtils.copyProperties(dest, src); - return true; - } catch (IllegalAccessException ex) { - Exceptions.printStackTrace(ex); - } catch (InvocationTargetException ex) { - Exceptions.printStackTrace(ex); - } - return false; - } -} +/* + * 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/java/com/jme3/util/mikktspace/MikkTSpaceContext.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java new file mode 100644 index 000000000..dc56ac240 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java @@ -0,0 +1,97 @@ +/* + * 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.util.mikktspace; + +/** + * + * @author Nehon + */ +public interface MikkTSpaceContext { + + /** + * Returns the number of faces (triangles/quads) on the mesh to be + * processed. + * + * @return + */ + public int getNumFaces(); + + /** + * Returns the number of vertices on face number iFace iFace is a number in + * the range {0, 1, ..., getNumFaces()-1} + * + * @param face + * @return + */ + public int getNumVerticesOfFace(int face); + + /** + * returns the position/normal/texcoord of the referenced face of vertex + * number iVert. iVert is in the range {0,1,2} for triangles and {0,1,2,3} + * for quads. + * + * @param posOut + * @param face + * @param vert + */ + public void getPosition(float posOut[], int face, int vert); + + public void getNormal(float normOut[], int face, int vert); + + public void getTexCoord(float texOut[], int face, int vert); + + /** + * The call-backsetTSpaceBasic() is sufficient for basic normal mapping. + * This function is used to return the tangent and sign to the application. + * tangent is a unit length vector. For normal maps it is sufficient to use + * the following simplified version of the bitangent which is generated at + * pixel/vertex level. + * + * bitangent = fSign * cross(vN, tangent); + * + * Note that the results are returned unindexed. It is possible to generate + * a new index list But averaging/overwriting tangent spaces by using an + * already existing index list WILL produce INCRORRECT results. DO NOT! use + * an already existing index list. + * + * @param tangent + * @param sign + * @param face + * @param vert + */ + public void setTSpaceBasic(float tangent[], float sign, int face, int vert); + + /** + * This function is used to return tangent space results to the application. + * tangent and biTangent are unit length vectors and fMagS and fMagT are + * their true magnitudes which can be used for relief mapping effects. + * + * biTangent is the "real" bitangent and thus may not be perpendicular to + * tangent. However, both are perpendicular to the vertex normal. For normal + * maps it is sufficient to use the following simplified version of the + * bitangent which is generated at pixel/vertex level. + * + *
+     * fSign = bIsOrientationPreserving ? 1.0f : (-1.0f);
+     * bitangent = fSign * cross(vN, tangent);
+     * 
+ * + * Note that the results are returned unindexed. It is possible to generate + * a new index list. But averaging/overwriting tangent spaces by using an + * already existing index list WILL produce INCRORRECT results. DO NOT! use + * an already existing index list. + * + * @param tangent + * @param biTangent + * @param magS + * @param magT + * @param isOrientationPreserving + * @param face + * @param vert + */ + void setTSpace(float tangent[], float biTangent[], float magS, float magT, + boolean isOrientationPreserving, int face, int vert); +} diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java new file mode 100644 index 000000000..190a2e2c4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceImpl.java @@ -0,0 +1,100 @@ +/* + * 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.util.mikktspace; + +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.util.BufferUtils; +import java.nio.FloatBuffer; + +/** + * + * @author Nehon + */ +public class MikkTSpaceImpl implements MikkTSpaceContext { + + Mesh mesh; + + public MikkTSpaceImpl(Mesh mesh) { + this.mesh = mesh; + VertexBuffer tangentBuffer = mesh.getBuffer(VertexBuffer.Type.Tangent); + if(tangentBuffer == null){ + FloatBuffer fb = BufferUtils.createFloatBuffer(mesh.getVertexCount() * 4); + mesh.setBuffer(VertexBuffer.Type.Tangent, 4, fb); + } + + //TODO ensure the Tangent buffer exists, else create one. + } + + @Override + public int getNumFaces() { + return mesh.getTriangleCount(); + } + + @Override + public int getNumVerticesOfFace(int face) { + return 3; + } + + @Override + public void getPosition(float[] posOut, int face, int vert) { + int vertIndex = getIndex(face, vert); + VertexBuffer position = mesh.getBuffer(VertexBuffer.Type.Position); + FloatBuffer pos = (FloatBuffer) position.getData(); + pos.position(vertIndex * 3); + posOut[0] = pos.get(); + posOut[1] = pos.get(); + posOut[2] = pos.get(); + } + + @Override + public void getNormal(float[] normOut, int face, int vert) { + int vertIndex = getIndex(face, vert); + VertexBuffer normal = mesh.getBuffer(VertexBuffer.Type.Normal); + FloatBuffer norm = (FloatBuffer) normal.getData(); + norm.position(vertIndex * 3); + normOut[0] = norm.get(); + normOut[1] = norm.get(); + normOut[2] = norm.get(); + } + + @Override + public void getTexCoord(float[] texOut, int face, int vert) { + int vertIndex = getIndex(face, vert); + VertexBuffer texCoord = mesh.getBuffer(VertexBuffer.Type.TexCoord); + FloatBuffer tex = (FloatBuffer) texCoord.getData(); + tex.position(vertIndex * 2); + texOut[0] = tex.get(); + texOut[1] = tex.get(); + } + + @Override + public void setTSpaceBasic(float[] tangent, float sign, int face, int vert) { + int vertIndex = getIndex(face, vert); + VertexBuffer tangentBuffer = mesh.getBuffer(VertexBuffer.Type.Tangent); + FloatBuffer tan = (FloatBuffer) tangentBuffer.getData(); + + tan.position(vertIndex * 4); + tan.put(tangent); + tan.put(sign); + + tan.rewind(); + tangentBuffer.setUpdateNeeded(); + } + + @Override + public void setTSpace(float[] tangent, float[] biTangent, float magS, float magT, boolean isOrientationPreserving, int face, int vert) { + //Do nothing + } + + private int getIndex(int face, int vert) { + IndexBuffer index = mesh.getIndexBuffer(); + int vertIndex = index.get(face * 3 + vert); + return vertIndex; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java new file mode 100644 index 000000000..af20b842b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikktspaceTangentGenerator.java @@ -0,0 +1,1717 @@ +/* + * 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.util.mikktspace; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This tangent generator is Highly experimental. + * This is the Java translation of The mikktspace generator made by Morten S. Mikkelsen + * C Source code can be found here + * https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.c + * https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.h + * + * MikkTspace looks like the new standard of tengent generation in 3D softwares. + * Xnormal, Blender, Substance painter, and many more use it. + * + * Usage is : + * MikkTSpaceTangentGenerator.generate(spatial); + * + * + * + * @author Nehon + */ +public class MikktspaceTangentGenerator { + + private final static int MARK_DEGENERATE = 1; + private final static int QUAD_ONE_DEGEN_TRI = 2; + private final static int GROUP_WITH_ANY = 4; + private final static int ORIENT_PRESERVING = 8; + private final static long INTERNAL_RND_SORT_SEED = 39871946 & 0xffffffffL; + static final int CELLS = 2048; + + static int makeIndex(final int face, final int vert) { + assert (vert >= 0 && vert < 4 && face >= 0); + return (face << 2) | (vert & 0x3); + } + + private static void indexToData(int[] face, int[] vert, final int indexIn) { + vert[0] = indexIn & 0x3; + face[0] = indexIn >> 2; + } + + static TSpace avgTSpace(final TSpace tS0, final TSpace tS1) { + TSpace tsRes = new TSpace(); + + // this if is important. Due to floating point precision + // averaging when s0 == s1 will cause a slight difference + // which results in tangent space splits later on + if (tS0.magS == tS1.magS && tS0.magT == tS1.magT && tS0.os.equals(tS1.os) && tS0.ot.equals(tS1.ot)) { + tsRes.magS = tS0.magS; + tsRes.magT = tS0.magT; + tsRes.os.set(tS0.os); + tsRes.ot.set(tS0.ot); + } else { + tsRes.magS = 0.5f * (tS0.magS + tS1.magS); + tsRes.magT = 0.5f * (tS0.magT + tS1.magT); + tsRes.os.set(tS0.os).addLocal(tS1.os).normalizeLocal(); + tsRes.ot.set(tS0.ot).addLocal(tS1.ot).normalizeLocal(); + } + return tsRes; + } + + public static void generate(Spatial s){ + if(s instanceof Node){ + Node n = (Node)s; + for (Spatial child : n.getChildren()) { + generate(child); + } + } else if (s instanceof Geometry){ + Geometry g = (Geometry)s; + MikkTSpaceImpl context = new MikkTSpaceImpl(g.getMesh()); + if(!genTangSpaceDefault(context)){ + Logger.getLogger(MikktspaceTangentGenerator.class.getName()).log(Level.SEVERE, "Failed to generate tangents for geometry " + g.getName()); + } + } + } + + public static boolean genTangSpaceDefault(MikkTSpaceContext mikkTSpace) { + return genTangSpace(mikkTSpace, 180.0f); + } + + public static boolean genTangSpace(MikkTSpaceContext mikkTSpace, final float angularThreshold) { + + // count nr_triangles + int[] piTriListIn; + int[] piGroupTrianglesBuffer; + TriInfo[] pTriInfos; + Group[] pGroups; + TSpace[] psTspace; + int iNrTrianglesIn = 0; + int iNrTSPaces, iTotTris, iDegenTriangles, iNrMaxGroups; + int iNrActiveGroups, index; + final int iNrFaces = mikkTSpace.getNumFaces(); + //boolean bRes = false; + final float fThresCos = (float) FastMath.cos((angularThreshold * (float) FastMath.PI) / 180.0f); + + // count triangles on supported faces + for (int f = 0; f < iNrFaces; f++) { + final int verts = mikkTSpace.getNumVerticesOfFace(f); + if (verts == 3) { + ++iNrTrianglesIn; + } else if (verts == 4) { + iNrTrianglesIn += 2; + } + } + if (iNrTrianglesIn <= 0) { + return false; + } + + piTriListIn = new int[3 * iNrTrianglesIn]; + pTriInfos = new TriInfo[iNrTrianglesIn]; + + // make an initial triangle -. face index list + iNrTSPaces = generateInitialVerticesIndexList(pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn); + + // make a welded index list of identical positions and attributes (pos, norm, texc) + generateSharedVerticesIndexList(piTriListIn, mikkTSpace, iNrTrianglesIn); + + // Mark all degenerate triangles + iTotTris = iNrTrianglesIn; + iDegenTriangles = 0; + for (int t = 0; t < iTotTris; t++) { + final int i0 = piTriListIn[t * 3 + 0]; + final int i1 = piTriListIn[t * 3 + 1]; + final int i2 = piTriListIn[t * 3 + 2]; + final Vector3f p0 = getPosition(mikkTSpace, i0); + final Vector3f p1 = getPosition(mikkTSpace, i1); + final Vector3f p2 = getPosition(mikkTSpace, i2); + if (p0.equals(p1) || p0.equals(p2) || p1.equals(p2)) {// degenerate + pTriInfos[t].flag |= MARK_DEGENERATE; + ++iDegenTriangles; + } + } + iNrTrianglesIn = iTotTris - iDegenTriangles; + + // mark all triangle pairs that belong to a quad with only one + // good triangle. These need special treatment in DegenEpilogue(). + // Additionally, move all good triangles to the start of + // pTriInfos[] and piTriListIn[] without changing order and + // put the degenerate triangles last. + degenPrologue(pTriInfos, piTriListIn, iNrTrianglesIn, iTotTris); + + // evaluate triangle level attributes and neighbor list + initTriInfo(pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn); + + // based on the 4 rules, identify groups based on connectivity + iNrMaxGroups = iNrTrianglesIn * 3; + pGroups = new Group[iNrMaxGroups]; + piGroupTrianglesBuffer = new int[iNrTrianglesIn * 3]; + + iNrActiveGroups + = build4RuleGroups(pTriInfos, pGroups, piGroupTrianglesBuffer, piTriListIn, iNrTrianglesIn); + + psTspace = new TSpace[iNrTSPaces]; + + for (int t = 0; t < iNrTSPaces; t++) { + TSpace tSpace = new TSpace(); + tSpace.os.set(1.0f, 0.0f, 0.0f); + tSpace.magS = 1.0f; + tSpace.ot.set(0.0f, 1.0f, 0.0f); + tSpace.magT = 1.0f; + psTspace[t] = tSpace; + } + + // make tspaces, each group is split up into subgroups if necessary + // based on fAngularThreshold. Finally a tangent space is made for + // every resulting subgroup + generateTSpaces(psTspace, pTriInfos, pGroups, iNrActiveGroups, piTriListIn, fThresCos, mikkTSpace); + + // degenerate quads with one good triangle will be fixed by copying a space from + // the good triangle to the coinciding vertex. + // all other degenerate triangles will just copy a space from any good triangle + // with the same welded index in piTriListIn[]. + DegenEpilogue(psTspace, pTriInfos, piTriListIn, mikkTSpace, iNrTrianglesIn, iTotTris); + + index = 0; + for (int f = 0; f < iNrFaces; f++) { + final int verts = mikkTSpace.getNumVerticesOfFace(f); + if (verts != 3 && verts != 4) { + continue; + } + + // I've decided to let degenerate triangles and group-with-anythings + // vary between left/right hand coordinate systems at the vertices. + // All healthy triangles on the other hand are built to always be either or. + + /*// force the coordinate system orientation to be uniform for every face. + // (this is already the case for good triangles but not for + // degenerate ones and those with bGroupWithAnything==true) + bool bOrient = psTspace[index].bOrient; + if (psTspace[index].iCounter == 0) // tspace was not derived from a group + { + // look for a space created in GenerateTSpaces() by iCounter>0 + bool bNotFound = true; + int i=1; + while (i 0) bNotFound=false; + else ++i; + } + if (!bNotFound) bOrient = psTspace[index+i].bOrient; + }*/ + // set data + for (int i = 0; i < verts; i++) { + final TSpace pTSpace = psTspace[index]; + float tang[] = {pTSpace.os.x, pTSpace.os.y, pTSpace.os.z}; + float bitang[] = {pTSpace.ot.x, pTSpace.ot.y, pTSpace.ot.z}; + mikkTSpace.setTSpace(tang, bitang, pTSpace.magS, pTSpace.magT, pTSpace.orient, f, i); + mikkTSpace.setTSpaceBasic(tang, pTSpace.orient == true ? 1.0f : (-1.0f), f, i); + ++index; + } + } + + return true; + } + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // it is IMPORTANT that this function is called to evaluate the hash since + // inlining could potentially reorder instructions and generate different + // results for the same effective input value fVal. + //TODO nehon: Wuuttt? something is fishy about this. How the fuck inlining can reorder instructions? Is that a C thing? + static int findGridCell(final float min, final float max, final float val) { + final float fIndex = CELLS * ((val - min) / (max - min)); + final int iIndex = (int) fIndex; + return iIndex < CELLS ? (iIndex >= 0 ? iIndex : 0) : (CELLS - 1); + } + + static void generateSharedVerticesIndexList(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) { + + // Generate bounding box + TmpVert[] pTmpVert; + Vector3f vMin = getPosition(mikkTSpace, 0); + Vector3f vMax = vMin.clone(); + Vector3f vDim; + float fMin, fMax; + for (int i = 1; i < (iNrTrianglesIn * 3); i++) { + final int index = piTriList_in_and_out[i]; + + final Vector3f vP = getPosition(mikkTSpace, index); + if (vMin.x > vP.x) { + vMin.x = vP.x; + } else if (vMax.x < vP.x) { + vMax.x = vP.x; + } + if (vMin.y > vP.y) { + vMin.y = vP.y; + } else if (vMax.y < vP.y) { + vMax.y = vP.y; + } + if (vMin.z > vP.z) { + vMin.z = vP.z; + } else if (vMax.z < vP.z) { + vMax.z = vP.z; + } + } + + vDim = vMax.subtract(vMin); + int iChannel = 0; + fMin = vMin.x; + fMax = vMax.x; + if (vDim.y > vDim.x && vDim.y > vDim.z) { + iChannel = 1; + fMin = vMin.y; + fMax = vMax.y; + } else if (vDim.z > vDim.x) { + iChannel = 2; + fMin = vMin.z; + fMax = vMax.z; + } + + //TODO Nehon: this is really fishy... seems like a hashtable implementation with nasty array manipulation... + int[] piHashTable = new int[iNrTrianglesIn * 3]; + int[] piHashCount = new int[CELLS]; + int[] piHashOffsets = new int[CELLS]; + int[] piHashCount2 = new int[CELLS]; + + // count amount of elements in each cell unit + for (int i = 0; i < (iNrTrianglesIn * 3); i++) { + final int index = piTriList_in_and_out[i]; + final Vector3f vP = getPosition(mikkTSpace, index); + final float fVal = iChannel == 0 ? vP.x : (iChannel == 1 ? vP.y : vP.z); + final int iCell = findGridCell(fMin, fMax, fVal); + ++piHashCount[iCell]; + } + + // evaluate start index of each cell. + piHashOffsets[0] = 0; + for (int k = 1; k < CELLS; k++) { + piHashOffsets[k] = piHashOffsets[k - 1] + piHashCount[k - 1]; + } + + // insert vertices + for (int i = 0; i < (iNrTrianglesIn * 3); i++) { + final int index = piTriList_in_and_out[i]; + final Vector3f vP = getPosition(mikkTSpace, index); + final float fVal = iChannel == 0 ? vP.x : (iChannel == 1 ? vP.y : vP.z); + final int iCell = findGridCell(fMin, fMax, fVal); + + assert (piHashCount2[iCell] < piHashCount[iCell]); + + // int * pTable = &piHashTable[piHashOffsets[iCell]]; + // pTable[piHashCount2[iCell]] = i; // vertex i has been inserted. + piHashTable[piHashOffsets[iCell] + piHashCount2[iCell]] = i;// vertex i has been inserted. + ++piHashCount2[iCell]; + } + for (int k = 0; k < CELLS; k++) { + assert (piHashCount2[k] == piHashCount[k]); // verify the count + } + + // find maximum amount of entries in any hash entry + int iMaxCount = piHashCount[0]; + for (int k = 1; k < CELLS; k++) { + if (iMaxCount < piHashCount[k]) { + iMaxCount = piHashCount[k]; + } + } + + pTmpVert = new TmpVert[iMaxCount]; + + // complete the merge + for (int k = 0; k < CELLS; k++) { + // extract table of cell k and amount of entries in it + // int * pTable = &piHashTable[piHashOffsets[k]]; + final int iEntries = piHashCount[k]; + if (iEntries < 2) { + continue; + } + + if (pTmpVert != null) { + for (int e = 0; e < iEntries; e++) { + int j = piHashTable[piHashOffsets[k] + e]; + final Vector3f vP = getPosition(mikkTSpace, piTriList_in_and_out[j]); + pTmpVert[e] = new TmpVert(); + pTmpVert[e].vert[0] = vP.x; + pTmpVert[e].vert[1] = vP.y; + pTmpVert[e].vert[2] = vP.z; + pTmpVert[e].index = j; + } + MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, 0, iEntries - 1); + } else { + //TODO Nehon: pTempVert is very unlikely to be null...maybe remove this... + int[] pTable = Arrays.copyOfRange(piHashTable, piHashOffsets[k], piHashOffsets[k] + iEntries); + MergeVertsSlow(piTriList_in_and_out, mikkTSpace, pTable, iEntries); + } + } + } + + static void MergeVertsFast(int piTriList_in_and_out[], TmpVert pTmpVert[], final MikkTSpaceContext mikkTSpace, final int iL_in, final int iR_in) { + // make bbox + float[] fvMin = new float[3]; + float[] fvMax = new float[3]; + for (int c = 0; c < 3; c++) { + fvMin[c] = pTmpVert[iL_in].vert[c]; + fvMax[c] = fvMin[c]; + } + for (int l = (iL_in + 1); l <= iR_in; l++) { + for (int c = 0; c < 3; c++) { + if (fvMin[c] > pTmpVert[l].vert[c]) { + fvMin[c] = pTmpVert[l].vert[c]; + } else if (fvMax[c] < pTmpVert[l].vert[c]) { + fvMax[c] = pTmpVert[l].vert[c]; + } + } + } + + float dx = fvMax[0] - fvMin[0]; + float dy = fvMax[1] - fvMin[1]; + float dz = fvMax[2] - fvMin[2]; + + int channel = 0; + if (dy > dx && dy > dz) { + channel = 1; + } else if (dz > dx) { + channel = 2; + } + + float fSep = 0.5f * (fvMax[channel] + fvMin[channel]); + + // terminate recursion when the separation/average value + // is no longer strictly between fMin and fMax values. + if (fSep >= fvMax[channel] || fSep <= fvMin[channel]) { + // complete the weld + for (int l = iL_in; l <= iR_in; l++) { + int i = pTmpVert[l].index; + final int index = piTriList_in_and_out[i]; + final Vector3f vP = getPosition(mikkTSpace, index); + final Vector3f vN = getNormal(mikkTSpace, index); + final Vector3f vT = getTexCoord(mikkTSpace, index); + + boolean bNotFound = true; + int l2 = iL_in, i2rec = -1; + while (l2 < l && bNotFound) { + final int i2 = pTmpVert[l2].index; + final int index2 = piTriList_in_and_out[i2]; + final Vector3f vP2 = getPosition(mikkTSpace, index2); + final Vector3f vN2 = getNormal(mikkTSpace, index2); + final Vector3f vT2 = getTexCoord(mikkTSpace, index2); + i2rec = i2; + + //if (vP==vP2 && vN==vN2 && vT==vT2) + if (vP.x == vP2.x && vP.y == vP2.y && vP.z == vP2.z + && vN.x == vN2.x && vN.y == vN2.y && vN.z == vN2.z + && vT.x == vT2.x && vT.y == vT2.y && vT.z == vT2.z) { + bNotFound = false; + } else { + ++l2; + } + } + + // merge if previously found + if (!bNotFound) { + piTriList_in_and_out[i] = piTriList_in_and_out[i2rec]; + } + } + } else { + int iL = iL_in, iR = iR_in; + assert ((iR_in - iL_in) > 0); // at least 2 entries + + // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] + while (iL < iR) { + boolean bReadyLeftSwap = false, bReadyRightSwap = false; + while ((!bReadyLeftSwap) && iL < iR) { + assert (iL >= iL_in && iL <= iR_in); + bReadyLeftSwap = !(pTmpVert[iL].vert[channel] < fSep); + if (!bReadyLeftSwap) { + ++iL; + } + } + while ((!bReadyRightSwap) && iL < iR) { + assert (iR >= iL_in && iR <= iR_in); + bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep; + if (!bReadyRightSwap) { + --iR; + } + } + assert ((iL < iR) || !(bReadyLeftSwap && bReadyRightSwap)); + + if (bReadyLeftSwap && bReadyRightSwap) { + final TmpVert sTmp = pTmpVert[iL]; + assert (iL < iR); + pTmpVert[iL] = pTmpVert[iR]; + pTmpVert[iR] = sTmp; + ++iL; + --iR; + } + } + + assert (iL == (iR + 1) || (iL == iR)); + if (iL == iR) { + final boolean bReadyRightSwap = pTmpVert[iR].vert[channel] < fSep; + if (bReadyRightSwap) { + ++iL; + } else { + --iR; + } + } + + // only need to weld when there is more than 1 instance of the (x,y,z) + if (iL_in < iR) { + MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, iL_in, iR); // weld all left of fSep + } + if (iL < iR_in) { + MergeVertsFast(piTriList_in_and_out, pTmpVert, mikkTSpace, iL, iR_in); // weld all right of (or equal to) fSep + } + } + } + + //TODO Nehon: Used only if an array failed to be allocated... Can't happen in Java... + static void MergeVertsSlow(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int pTable[], final int iEntries) { + // this can be optimized further using a tree structure or more hashing. + for (int e = 0; e < iEntries; e++) { + int i = pTable[e]; + final int index = piTriList_in_and_out[i]; + final Vector3f vP = getPosition(mikkTSpace, index); + final Vector3f vN = getNormal(mikkTSpace, index); + final Vector3f vT = getTexCoord(mikkTSpace, index); + + boolean bNotFound = true; + int e2 = 0, i2rec = -1; + while (e2 < e && bNotFound) { + final int i2 = pTable[e2]; + final int index2 = piTriList_in_and_out[i2]; + final Vector3f vP2 = getPosition(mikkTSpace, index2); + final Vector3f vN2 = getNormal(mikkTSpace, index2); + final Vector3f vT2 = getTexCoord(mikkTSpace, index2); + i2rec = i2; + + if (vP.equals(vP2) && vN.equals(vN2) && vT.equals(vT2)) { + bNotFound = false; + } else { + ++e2; + } + } + + // merge if previously found + if (!bNotFound) { + piTriList_in_and_out[i] = piTriList_in_and_out[i2rec]; + } + } + } + + //TODO Nehon : Not used...seemsit's used in the original version if the structure to store the data in the regular method failed... + static void generateSharedVerticesIndexListSlow(int piTriList_in_and_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) { + int iNumUniqueVerts = 0; + for (int t = 0; t < iNrTrianglesIn; t++) { + for (int i = 0; i < 3; i++) { + final int offs = t * 3 + i; + final int index = piTriList_in_and_out[offs]; + + final Vector3f vP = getPosition(mikkTSpace, index); + final Vector3f vN = getNormal(mikkTSpace, index); + final Vector3f vT = getTexCoord(mikkTSpace, index); + + boolean bFound = false; + int t2 = 0, index2rec = -1; + while (!bFound && t2 <= t) { + int j = 0; + while (!bFound && j < 3) { + final int index2 = piTriList_in_and_out[t2 * 3 + j]; + final Vector3f vP2 = getPosition(mikkTSpace, index2); + final Vector3f vN2 = getNormal(mikkTSpace, index2); + final Vector3f vT2 = getTexCoord(mikkTSpace, index2); + + if (vP.equals(vP2) && vN.equals(vN2) && vT.equals(vT2)) { + bFound = true; + } else { + ++j; + } + } + if (!bFound) { + ++t2; + } + } + + assert (bFound); + // if we found our own + if (index2rec == index) { + ++iNumUniqueVerts; + } + + piTriList_in_and_out[offs] = index2rec; + } + } + } + + static int generateInitialVerticesIndexList(TriInfo pTriInfos[], int piTriList_out[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) { + int iTSpacesOffs = 0; + int iDstTriIndex = 0; + for (int f = 0; f < mikkTSpace.getNumFaces(); f++) { + final int verts = mikkTSpace.getNumVerticesOfFace(f); + if (verts != 3 && verts != 4) { + continue; + } + + //TODO nehon : clean this, have a local TrinInfo and assign it to pTriInfo[iDstTriIndex] at the end... and change those variables names... + pTriInfos[iDstTriIndex] = new TriInfo(); + pTriInfos[iDstTriIndex].orgFaceNumber = f; + pTriInfos[iDstTriIndex].tSpacesOffs = iTSpacesOffs; + + if (verts == 3) { + //TODO same here it should be easy once the local TriInfo is created. + byte[] pVerts = pTriInfos[iDstTriIndex].vertNum; + pVerts[0] = 0; + pVerts[1] = 1; + pVerts[2] = 2; + piTriList_out[iDstTriIndex * 3 + 0] = makeIndex(f, 0); + piTriList_out[iDstTriIndex * 3 + 1] = makeIndex(f, 1); + piTriList_out[iDstTriIndex * 3 + 2] = makeIndex(f, 2); + ++iDstTriIndex; // next + } else { + //Note, Nehon: we should never get there with JME, because we don't support quads... + //but I'm going to let it there incase someone needs it... Just know this code is not tested. + {//TODO remove those useless brackets... + pTriInfos[iDstTriIndex + 1].orgFaceNumber = f; + pTriInfos[iDstTriIndex + 1].tSpacesOffs = iTSpacesOffs; + } + + { + // need an order independent way to evaluate + // tspace on quads. This is done by splitting + // along the shortest diagonal. + final int i0 = makeIndex(f, 0); + final int i1 = makeIndex(f, 1); + final int i2 = makeIndex(f, 2); + final int i3 = makeIndex(f, 3); + final Vector3f T0 = getTexCoord(mikkTSpace, i0); + final Vector3f T1 = getTexCoord(mikkTSpace, i1); + final Vector3f T2 = getTexCoord(mikkTSpace, i2); + final Vector3f T3 = getTexCoord(mikkTSpace, i3); + final float distSQ_02 = T2.subtract(T0).lengthSquared(); + final float distSQ_13 = T3.subtract(T1).lengthSquared(); + boolean bQuadDiagIs_02; + if (distSQ_02 < distSQ_13) { + bQuadDiagIs_02 = true; + } else if (distSQ_13 < distSQ_02) { + bQuadDiagIs_02 = false; + } else { + final Vector3f P0 = getPosition(mikkTSpace, i0); + final Vector3f P1 = getPosition(mikkTSpace, i1); + final Vector3f P2 = getPosition(mikkTSpace, i2); + final Vector3f P3 = getPosition(mikkTSpace, i3); + final float distSQ_022 = P2.subtract(P0).lengthSquared(); + final float distSQ_132 = P3.subtract(P1).lengthSquared(); + + bQuadDiagIs_02 = distSQ_132 >= distSQ_022; + } + + if (bQuadDiagIs_02) { + { + byte[] pVerts_A = pTriInfos[iDstTriIndex].vertNum; + pVerts_A[0] = 0; + pVerts_A[1] = 1; + pVerts_A[2] = 2; + } + piTriList_out[iDstTriIndex * 3 + 0] = i0; + piTriList_out[iDstTriIndex * 3 + 1] = i1; + piTriList_out[iDstTriIndex * 3 + 2] = i2; + ++iDstTriIndex; // next + { + byte[] pVerts_B = pTriInfos[iDstTriIndex].vertNum; + pVerts_B[0] = 0; + pVerts_B[1] = 2; + pVerts_B[2] = 3; + } + piTriList_out[iDstTriIndex * 3 + 0] = i0; + piTriList_out[iDstTriIndex * 3 + 1] = i2; + piTriList_out[iDstTriIndex * 3 + 2] = i3; + ++iDstTriIndex; // next + } else { + { + byte[] pVerts_A = pTriInfos[iDstTriIndex].vertNum; + pVerts_A[0] = 0; + pVerts_A[1] = 1; + pVerts_A[2] = 3; + } + piTriList_out[iDstTriIndex * 3 + 0] = i0; + piTriList_out[iDstTriIndex * 3 + 1] = i1; + piTriList_out[iDstTriIndex * 3 + 2] = i3; + ++iDstTriIndex; // next + { + byte[] pVerts_B = pTriInfos[iDstTriIndex].vertNum; + pVerts_B[0] = 1; + pVerts_B[1] = 2; + pVerts_B[2] = 3; + } + piTriList_out[iDstTriIndex * 3 + 0] = i1; + piTriList_out[iDstTriIndex * 3 + 1] = i2; + piTriList_out[iDstTriIndex * 3 + 2] = i3; + ++iDstTriIndex; // next + } + } + } + + iTSpacesOffs += verts; + assert (iDstTriIndex <= iNrTrianglesIn); + } + + for (int t = 0; t < iNrTrianglesIn; t++) { + pTriInfos[t].flag = 0; + } + + // return total amount of tspaces + return iTSpacesOffs; + } + + static Vector3f getPosition(final MikkTSpaceContext mikkTSpace, final int index) { + //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData + int[] iF = new int[1]; + int[] iI = new int[1]; + float[] pos = new float[3]; + indexToData(iF, iI, index); + mikkTSpace.getPosition(pos, iF[0], iI[0]); + return new Vector3f(pos[0], pos[1], pos[2]); + } + + static Vector3f getNormal(final MikkTSpaceContext mikkTSpace, final int index) { + //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData + int[] iF = new int[1]; + int[] iI = new int[1]; + float[] norm = new float[3]; + indexToData(iF, iI, index); + mikkTSpace.getNormal(norm, iF[0], iI[0]); + return new Vector3f(norm[0], norm[1], norm[2]); + } + + static Vector3f getTexCoord(final MikkTSpaceContext mikkTSpace, final int index) { + //TODO nehon: very ugly but works... using arrays to pass integers as references in the indexToData + int[] iF = new int[1]; + int[] iI = new int[1]; + float[] texc = new float[2]; + indexToData(iF, iI, index); + mikkTSpace.getTexCoord(texc, iF[0], iI[0]); + return new Vector3f(texc[0], texc[1], 1.0f); + } + + // returns the texture area times 2 + static float calcTexArea(final MikkTSpaceContext mikkTSpace, final int indices[]) { + final Vector3f t1 = getTexCoord(mikkTSpace, indices[0]); + final Vector3f t2 = getTexCoord(mikkTSpace, indices[1]); + final Vector3f t3 = getTexCoord(mikkTSpace, indices[2]); + + final float t21x = t2.x - t1.x; + final float t21y = t2.y - t1.y; + final float t31x = t3.x - t1.x; + final float t31y = t3.y - t1.y; + + final float fSignedAreaSTx2 = t21x * t31y - t21y * t31x; + + return fSignedAreaSTx2 < 0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; + } + + private static boolean isNotZero(float v) { + return Math.abs(v) > 0; + } + + static void initTriInfo(TriInfo pTriInfos[], final int piTriListIn[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn) { + + // pTriInfos[f].flag is cleared in GenerateInitialVerticesIndexList() which is called before this function. + // generate neighbor info list + for (int f = 0; f < iNrTrianglesIn; f++) { + for (int i = 0; i < 3; i++) { + pTriInfos[f].faceNeighbors[i] = -1; + pTriInfos[f].assignedGroup[i] = null; + + pTriInfos[f].os.x = 0.0f; + pTriInfos[f].os.y = 0.0f; + pTriInfos[f].os.z = 0.0f; + pTriInfos[f].ot.x = 0.0f; + pTriInfos[f].ot.y = 0.0f; + pTriInfos[f].ot.z = 0.0f; + pTriInfos[f].magS = 0; + pTriInfos[f].magT = 0; + + // assumed bad + pTriInfos[f].flag |= GROUP_WITH_ANY; + } + } + + // evaluate first order derivatives + for (int f = 0; f < iNrTrianglesIn; f++) { + // initial values + final Vector3f v1 = getPosition(mikkTSpace, piTriListIn[f * 3 + 0]); + final Vector3f v2 = getPosition(mikkTSpace, piTriListIn[f * 3 + 1]); + final Vector3f v3 = getPosition(mikkTSpace, piTriListIn[f * 3 + 2]); + final Vector3f t1 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 0]); + final Vector3f t2 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 1]); + final Vector3f t3 = getTexCoord(mikkTSpace, piTriListIn[f * 3 + 2]); + + final float t21x = t2.x - t1.x; + final float t21y = t2.y - t1.y; + final float t31x = t3.x - t1.x; + final float t31y = t3.y - t1.y; + final Vector3f d1 = v2.subtract(v1); + final Vector3f d2 = v3.subtract(v1); + + final float fSignedAreaSTx2 = t21x * t31y - t21y * t31x; + //assert(fSignedAreaSTx2!=0); + Vector3f vOs = d1.mult(t31y).subtract(d2.mult(t21y)); // eq 18 + Vector3f vOt = d1.mult(-t31x).add(d2.mult(t21x)); // eq 19 + + pTriInfos[f].flag |= (fSignedAreaSTx2 > 0 ? ORIENT_PRESERVING : 0); + + if (isNotZero(fSignedAreaSTx2)) { + final float fAbsArea = Math.abs(fSignedAreaSTx2); + final float fLenOs = vOs.length(); + final float fLenOt = vOt.length(); + final float fS = (pTriInfos[f].flag & ORIENT_PRESERVING) == 0 ? (-1.0f) : 1.0f; + if (isNotZero(fLenOs)) { + pTriInfos[f].os = vOs.multLocal(fS / fLenOs); + } + if (isNotZero(fLenOt)) { + pTriInfos[f].ot = vOt.multLocal(fS / fLenOt); + } + + // evaluate magnitudes prior to normalization of vOs and vOt + pTriInfos[f].magS = fLenOs / fAbsArea; + pTriInfos[f].magT = fLenOt / fAbsArea; + + // if this is a good triangle + if (isNotZero(pTriInfos[f].magS) && isNotZero(pTriInfos[f].magT)) { + pTriInfos[f].flag &= (~GROUP_WITH_ANY); + } + } + } + + // force otherwise healthy quads to a fixed orientation + int t = 0; + while (t < (iNrTrianglesIn - 1)) { + final int iFO_a = pTriInfos[t].orgFaceNumber; + final int iFO_b = pTriInfos[t + 1].orgFaceNumber; + if (iFO_a == iFO_b) { + // this is a quad + final boolean bIsDeg_a = (pTriInfos[t].flag & MARK_DEGENERATE) != 0; + final boolean bIsDeg_b = (pTriInfos[t + 1].flag & MARK_DEGENERATE) != 0; + + // bad triangles should already have been removed by + // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false + if ((bIsDeg_a || bIsDeg_b) == false) { + final boolean bOrientA = (pTriInfos[t].flag & ORIENT_PRESERVING) != 0; + final boolean bOrientB = (pTriInfos[t + 1].flag & ORIENT_PRESERVING) != 0; + // if this happens the quad has extremely bad mapping!! + if (bOrientA != bOrientB) { + //printf("found quad with bad mapping\n"); + boolean bChooseOrientFirstTri = false; + if ((pTriInfos[t + 1].flag & GROUP_WITH_ANY) != 0) { + bChooseOrientFirstTri = true; + } else if (calcTexArea(mikkTSpace, Arrays.copyOfRange(piTriListIn, t * 3 + 0, t * 3 + 3)) >= calcTexArea(mikkTSpace, Arrays.copyOfRange(piTriListIn, (t + 1) * 3 + 0, (t + 1) * 3 + 3))) { + bChooseOrientFirstTri = true; + } + + // force match + { + final int t0 = bChooseOrientFirstTri ? t : (t + 1); + final int t1 = bChooseOrientFirstTri ? (t + 1) : t; + pTriInfos[t1].flag &= (~ORIENT_PRESERVING); // clear first + pTriInfos[t1].flag |= (pTriInfos[t0].flag & ORIENT_PRESERVING); // copy bit + } + } + } + t += 2; + } else { + ++t; + } + } + + // match up edge pairs + { + //Edge * pEdges = (Edge *) malloc(sizeof(Edge)*iNrTrianglesIn*3); + Edge[] pEdges = new Edge[iNrTrianglesIn * 3]; + + //TODO nehon weird... original algorithm check if pEdges is null but it's just been allocated... weirder, it does soemthing different if the edges are null... + // if (pEdges==null) + // BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); + // else + // { + buildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); + + // } + } + } + + static int build4RuleGroups(TriInfo pTriInfos[], Group pGroups[], int piGroupTrianglesBuffer[], final int piTriListIn[], final int iNrTrianglesIn) { + final int iNrMaxGroups = iNrTrianglesIn * 3; + int iNrActiveGroups = 0; + int iOffset = 0; + // (void)iNrMaxGroups; /* quiet warnings in non debug mode */ + for (int f = 0; f < iNrTrianglesIn; f++) { + for (int i = 0; i < 3; i++) { + // if not assigned to a group + if ((pTriInfos[f].flag & GROUP_WITH_ANY) == 0 && pTriInfos[f].assignedGroup[i] == null) { + boolean bOrPre; + final int vert_index = piTriListIn[f * 3 + i]; + assert (iNrActiveGroups < iNrMaxGroups); + pTriInfos[f].assignedGroup[i] = new Group(); + pGroups[iNrActiveGroups] = pTriInfos[f].assignedGroup[i]; + pTriInfos[f].assignedGroup[i].vertexRepresentitive = vert_index; + pTriInfos[f].assignedGroup[i].orientPreservering = (pTriInfos[f].flag & ORIENT_PRESERVING) != 0; + pTriInfos[f].assignedGroup[i].nrFaces = 0; + + ++iNrActiveGroups; + + addTriToGroup(pTriInfos[f].assignedGroup[i], f); + bOrPre = (pTriInfos[f].flag & ORIENT_PRESERVING) != 0; + int neigh_indexL = pTriInfos[f].faceNeighbors[i]; + int neigh_indexR = pTriInfos[f].faceNeighbors[i > 0 ? (i - 1) : 2]; + if (neigh_indexL >= 0) { + // neighbor + final boolean bAnswer + = assignRecur(piTriListIn, pTriInfos, neigh_indexL, + pTriInfos[f].assignedGroup[i]); + + final boolean bOrPre2 = (pTriInfos[neigh_indexL].flag & ORIENT_PRESERVING) != 0; + final boolean bDiff = bOrPre != bOrPre2; + assert (bAnswer || bDiff); + //(void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + if (neigh_indexR >= 0) { + // neighbor + final boolean bAnswer + = assignRecur(piTriListIn, pTriInfos, neigh_indexR, + pTriInfos[f].assignedGroup[i]); + + final boolean bOrPre2 = (pTriInfos[neigh_indexR].flag & ORIENT_PRESERVING) != 0; + final boolean bDiff = bOrPre != bOrPre2; + assert (bAnswer || bDiff); + //(void)bAnswer, (void)bDiff; /* quiet warnings in non debug mode */ + } + + int[] faceIndices = new int[pTriInfos[f].assignedGroup[i].nrFaces]; + //pTriInfos[f].assignedGroup[i].faceIndices.toArray(faceIndices); + for (int j = 0; j < faceIndices.length; j++) { + faceIndices[j] = pTriInfos[f].assignedGroup[i].faceIndices.get(j); + } + + //Nehon: copy back the faceIndices data into the groupTriangleBuffer. + System.arraycopy( faceIndices, 0, piGroupTrianglesBuffer, iOffset, pTriInfos[f].assignedGroup[i].nrFaces); + // update offset + iOffset += pTriInfos[f].assignedGroup[i].nrFaces; + // since the groups are disjoint a triangle can never + // belong to more than 3 groups. Subsequently something + // is completely screwed if this assertion ever hits. + assert (iOffset <= iNrMaxGroups); + } + } + } + + return iNrActiveGroups; + } + + static void addTriToGroup(Group group, final int triIndex) { + //group.faceIndices[group.nrFaces] = triIndex; + group.faceIndices.add(triIndex); + ++group.nrFaces; + } + + static boolean assignRecur(final int piTriListIn[], TriInfo psTriInfos[], final int iMyTriIndex, Group pGroup) { + TriInfo pMyTriInfo = psTriInfos[iMyTriIndex]; + + // track down vertex + final int iVertRep = pGroup.vertexRepresentitive; + int index = 3 * iMyTriIndex; + int i = -1; + if (piTriListIn[index] == iVertRep) { + i = 0; + } else if (piTriListIn[index + 1] == iVertRep) { + i = 1; + } else if (piTriListIn[index + 2] == iVertRep) { + i = 2; + } + assert (i >= 0 && i < 3); + + // early out + if (pMyTriInfo.assignedGroup[i] == pGroup) { + return true; + } else if (pMyTriInfo.assignedGroup[i] != null) { + return false; + } + if ((pMyTriInfo.flag & GROUP_WITH_ANY) != 0) { + // first to group with a group-with-anything triangle + // determines it's orientation. + // This is the only existing order dependency in the code!! + if (pMyTriInfo.assignedGroup[0] == null + && pMyTriInfo.assignedGroup[1] == null + && pMyTriInfo.assignedGroup[2] == null) { + pMyTriInfo.flag &= (~ORIENT_PRESERVING); + pMyTriInfo.flag |= (pGroup.orientPreservering ? ORIENT_PRESERVING : 0); + } + } + { + final boolean bOrient = (pMyTriInfo.flag & ORIENT_PRESERVING) != 0; + if (bOrient != pGroup.orientPreservering) { + return false; + } + } + + addTriToGroup(pGroup, iMyTriIndex); + pMyTriInfo.assignedGroup[i] = pGroup; + + { + final int neigh_indexL = pMyTriInfo.faceNeighbors[i]; + final int neigh_indexR = pMyTriInfo.faceNeighbors[i > 0 ? (i - 1) : 2]; + if (neigh_indexL >= 0) { + assignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); + } + if (neigh_indexR >= 0) { + assignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); + } + } + + return true; + } + + static boolean generateTSpaces(TSpace psTspace[], final TriInfo pTriInfos[], final Group pGroups[], + final int iNrActiveGroups, final int piTriListIn[], final float fThresCos, + final MikkTSpaceContext mikkTSpace) { + TSpace[] pSubGroupTspace; + SubGroup[] pUniSubGroups; + int[] pTmpMembers; + int iMaxNrFaces = 0, iUniqueTspaces = 0, g = 0, i = 0; + for (g = 0; g < iNrActiveGroups; g++) { + if (iMaxNrFaces < pGroups[g].nrFaces) { + iMaxNrFaces = pGroups[g].nrFaces; + } + } + + if (iMaxNrFaces == 0) { + return true; + } + + // make initial allocations + pSubGroupTspace = new TSpace[iMaxNrFaces]; + pUniSubGroups = new SubGroup[iMaxNrFaces]; + pTmpMembers = new int[iMaxNrFaces]; + + + iUniqueTspaces = 0; + for (g = 0; g < iNrActiveGroups; g++) { + final Group pGroup = pGroups[g]; + int iUniqueSubGroups = 0, s = 0; + + for (i = 0; i < pGroup.nrFaces; i++) // triangles + { + final int f = pGroup.faceIndices.get(i); // triangle number + int index = -1, iVertIndex = -1, iOF_1 = -1, iMembers = 0, j = 0, l = 0; + SubGroup tmp_group = new SubGroup(); + boolean bFound; + Vector3f n, vOs, vOt; + if (pTriInfos[f].assignedGroup[0] == pGroup) { + index = 0; + } else if (pTriInfos[f].assignedGroup[1] == pGroup) { + index = 1; + } else if (pTriInfos[f].assignedGroup[2] == pGroup) { + index = 2; + } + assert (index >= 0 && index < 3); + + iVertIndex = piTriListIn[f * 3 + index]; + assert (iVertIndex == pGroup.vertexRepresentitive); + + // is normalized already + n = getNormal(mikkTSpace, iVertIndex); + + // project + vOs = pTriInfos[f].os.subtract(n.mult(n.dot(pTriInfos[f].os))); + vOt = pTriInfos[f].ot.subtract(n.mult(n.dot(pTriInfos[f].ot))); + vOs.normalizeLocal(); + vOt.normalizeLocal(); + + // original face number + iOF_1 = pTriInfos[f].orgFaceNumber; + + iMembers = 0; + for (j = 0; j < pGroup.nrFaces; j++) { + final int t = pGroup.faceIndices.get(j); // triangle number + final int iOF_2 = pTriInfos[t].orgFaceNumber; + + // project + Vector3f vOs2 = pTriInfos[t].os.subtract(n.mult(n.dot(pTriInfos[t].os))); + Vector3f vOt2 = pTriInfos[t].ot.subtract(n.mult(n.dot(pTriInfos[t].ot))); + vOs2.normalizeLocal(); + vOt2.normalizeLocal(); + + { + final boolean bAny = ((pTriInfos[f].flag | pTriInfos[t].flag) & GROUP_WITH_ANY) != 0; + // make sure triangles which belong to the same quad are joined. + final boolean bSameOrgFace = iOF_1 == iOF_2; + + final float fCosS = vOs.dot(vOs2); + final float fCosT = vOt.dot(vOt2); + + assert (f != t || bSameOrgFace); // sanity check + if (bAny || bSameOrgFace || (fCosS > fThresCos && fCosT > fThresCos)) { + pTmpMembers[iMembers++] = t; + } + } + } + + // sort pTmpMembers + tmp_group.nrFaces = iMembers; + tmp_group.triMembers = pTmpMembers; + if (iMembers > 1) { + quickSort(pTmpMembers, 0, iMembers - 1, INTERNAL_RND_SORT_SEED); + } + + // look for an existing match + bFound = false; + l = 0; + while (l < iUniqueSubGroups && !bFound) { + bFound = compareSubGroups(tmp_group, pUniSubGroups[l]); + if (!bFound) { + ++l; + } + } + + // assign tangent space index + assert (bFound || l == iUniqueSubGroups); + //piTempTangIndices[f*3+index] = iUniqueTspaces+l; + + // if no match was found we allocate a new subgroup + if (!bFound) { + // insert new subgroup + int[] pIndices = new int[iMembers]; + pUniSubGroups[iUniqueSubGroups] = new SubGroup(); + pUniSubGroups[iUniqueSubGroups].nrFaces = iMembers; + pUniSubGroups[iUniqueSubGroups].triMembers = pIndices; + System.arraycopy(tmp_group.triMembers, 0, pIndices, 0, iMembers); + //memcpy(pIndices, tmp_group.pTriMembers, iMembers*sizeof(int)); + pSubGroupTspace[iUniqueSubGroups] + = evalTspace(tmp_group.triMembers, iMembers, piTriListIn, pTriInfos, mikkTSpace, pGroup.vertexRepresentitive); + ++iUniqueSubGroups; + } + + // output tspace + { + final int iOffs = pTriInfos[f].tSpacesOffs; + final int iVert = pTriInfos[f].vertNum[index]; + TSpace pTS_out = psTspace[iOffs + iVert]; + assert (pTS_out.counter < 2); + assert (((pTriInfos[f].flag & ORIENT_PRESERVING) != 0) == pGroup.orientPreservering); + if (pTS_out.counter == 1) { + pTS_out.set(avgTSpace(pTS_out, pSubGroupTspace[l])); + pTS_out.counter = 2; // update counter + pTS_out.orient = pGroup.orientPreservering; + } else { + assert (pTS_out.counter == 0); + pTS_out.set(pSubGroupTspace[l]); + pTS_out.counter = 1; // update counter + pTS_out.orient = pGroup.orientPreservering; + } + } + } + + iUniqueTspaces += iUniqueSubGroups; + } + + return true; + } + + static TSpace evalTspace(int face_indices[], final int iFaces, final int piTriListIn[], final TriInfo pTriInfos[], + final MikkTSpaceContext mikkTSpace, final int iVertexRepresentitive) { + TSpace res = new TSpace(); + float fAngleSum = 0; + + for (int face = 0; face < iFaces; face++) { + final int f = face_indices[face]; + + // only valid triangles get to add their contribution + if ((pTriInfos[f].flag & GROUP_WITH_ANY) == 0) { + + int i = -1; + if (piTriListIn[3 * f + 0] == iVertexRepresentitive) { + i = 0; + } else if (piTriListIn[3 * f + 1] == iVertexRepresentitive) { + i = 1; + } else if (piTriListIn[3 * f + 2] == iVertexRepresentitive) { + i = 2; + } + assert (i >= 0 && i < 3); + + // project + int index = piTriListIn[3 * f + i]; + Vector3f n = getNormal(mikkTSpace, index); + Vector3f vOs = pTriInfos[f].os.subtract(n.mult(n.dot(pTriInfos[f].os))); + Vector3f vOt = pTriInfos[f].ot.subtract(n.mult(n.dot(pTriInfos[f].ot))); + vOs.normalizeLocal(); + vOt.normalizeLocal(); + + int i2 = piTriListIn[3 * f + (i < 2 ? (i + 1) : 0)]; + int i1 = piTriListIn[3 * f + i]; + int i0 = piTriListIn[3 * f + (i > 0 ? (i - 1) : 2)]; + + Vector3f p0 = getPosition(mikkTSpace, i0); + Vector3f p1 = getPosition(mikkTSpace, i1); + Vector3f p2 = getPosition(mikkTSpace, i2); + Vector3f v1 = p0.subtract(p1); + Vector3f v2 = p2.subtract(p1); + + // project + v1.subtractLocal(n.mult(n.dot(v1))).normalizeLocal(); + v2.subtractLocal(n.mult(n.dot(v2))).normalizeLocal(); + + // weight contribution by the angle + // between the two edge vectors + float fCos = v1.dot(v2); + fCos = fCos > 1 ? 1 : (fCos < (-1) ? (-1) : fCos); + float fAngle = (float) Math.acos(fCos); + float fMagS = pTriInfos[f].magS; + float fMagT = pTriInfos[f].magT; + + res.os.addLocal(vOs.multLocal(fAngle)); + res.ot.addLocal(vOt.multLocal(fAngle)); + res.magS += (fAngle * fMagS); + res.magT += (fAngle * fMagT); + fAngleSum += fAngle; + } + } + + // normalize + res.os.normalizeLocal(); + res.ot.normalizeLocal(); + + if (fAngleSum > 0) { + res.magS /= fAngleSum; + res.magT /= fAngleSum; + } + + return res; + } + + static boolean compareSubGroups(final SubGroup pg1, final SubGroup pg2) { + if(pg2 == null || (pg1.nrFaces != pg2.nrFaces)){ + return false; + } + boolean stillSame = true; + int i = 0; + while (i < pg1.nrFaces && stillSame) { + stillSame = pg1.triMembers[i] == pg2.triMembers[i]; + if (stillSame) { + ++i; + } + } + return stillSame; + } + + static void quickSort(int[] pSortBuffer, int iLeft, int iRight, long uSeed) { + int iL, iR, n, index, iMid, iTmp; + + // Random + long t = uSeed & 31; + t = (uSeed << t) | (uSeed >> (32 - t)); + uSeed = uSeed + t + 3; + // Random end + uSeed = uSeed & 0xffffffffL; + + iL = iLeft; + iR = iRight; + n = (iR - iL) + 1; + assert (n >= 0); + index = (int) ((uSeed & 0xffffffffL) % n); + + iMid = pSortBuffer[index + iL]; + + do { + while (pSortBuffer[iL] < iMid) { + ++iL; + } + while (pSortBuffer[iR] > iMid) { + --iR; + } + + if (iL <= iR) { + iTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = iTmp; + ++iL; + --iR; + } + } while (iL <= iR); + + if (iLeft < iR) { + quickSort(pSortBuffer, iLeft, iR, uSeed); + } + if (iL < iRight) { + quickSort(pSortBuffer, iL, iRight, uSeed); + } + } + + static void buildNeighborsFast(TriInfo pTriInfos[], Edge[] pEdges, final int piTriListIn[], final int iNrTrianglesIn) { + // build array of edges + long uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? + + for (int f = 0; f < iNrTrianglesIn; f++) { + for (int i = 0; i < 3; i++) { + final int i0 = piTriListIn[f * 3 + i]; + final int i1 = piTriListIn[f * 3 + (i < 2 ? (i + 1) : 0)]; + pEdges[f * 3 + i] = new Edge(); + pEdges[f * 3 + i].setI0(i0 < i1 ? i0 : i1); // put minimum index in i0 + pEdges[f * 3 + i].setI1(!(i0 < i1) ? i0 : i1); // put maximum index in i1 + pEdges[f * 3 + i].setF(f); // record face number + } + } + + // sort over all edges by i0, this is the pricy one. + quickSortEdges(pEdges, 0, iNrTrianglesIn * 3 - 1, 0, uSeed); // sort channel 0 which is i0 + + // sub sort over i1, should be fast. + // could replace this with a 64 bit int sort over (i0,i1) + // with i0 as msb in the quicksort call above. + int iEntries = iNrTrianglesIn * 3; + int iCurStartIndex = 0; + for (int i = 1; i < iEntries; i++) { + if (pEdges[iCurStartIndex].getI0() != pEdges[i].getI0()) { + final int iL = iCurStartIndex; + final int iR = i - 1; + //final int iElems = i-iL; + iCurStartIndex = i; + quickSortEdges(pEdges, iL, iR, 1, uSeed); // sort channel 1 which is i1 + } + } + + // sub sort over f, which should be fast. + // this step is to remain compliant with BuildNeighborsSlow() when + // more than 2 triangles use the same edge (such as a butterfly topology). + iCurStartIndex = 0; + for (int i = 1; i < iEntries; i++) { + if (pEdges[iCurStartIndex].getI0() != pEdges[i].getI0() || pEdges[iCurStartIndex].getI1() != pEdges[i].getI1()) { + final int iL = iCurStartIndex; + final int iR = i - 1; + //final int iElems = i-iL; + iCurStartIndex = i; + quickSortEdges(pEdges, iL, iR, 2, uSeed); // sort channel 2 which is f + } + } + + // pair up, adjacent triangles + for (int i = 0; i < iEntries; i++) { + final int i0 = pEdges[i].getI0(); + final int i1 = pEdges[i].getI1(); + final int g = pEdges[i].getF(); + boolean bUnassigned_A; + + int[] i0_A = new int[1]; + int[] i1_A = new int[1]; + int[] edgenum_A = new int[1]; + int[] edgenum_B = new int[1]; + //int edgenum_B=0; // 0,1 or 2 + int[] triList = new int[3]; + System.arraycopy(piTriListIn, g * 3, triList, 0, 3); + getEdge(i0_A, i1_A, edgenum_A, triList, i0, i1); // resolve index ordering and edge_num + bUnassigned_A = pTriInfos[g].faceNeighbors[edgenum_A[0]] == -1; + + if (bUnassigned_A) { + // get true index ordering + int j = i + 1, t; + boolean bNotFound = true; + while (j < iEntries && i0 == pEdges[j].getI0() && i1 == pEdges[j].getI1() && bNotFound) { + boolean bUnassigned_B; + int[] i0_B = new int[1]; + int[] i1_B = new int[1]; + t = pEdges[j].getF(); + // flip i0_B and i1_B + System.arraycopy(piTriListIn, t * 3, triList, 0, 3); + getEdge(i1_B, i0_B, edgenum_B, triList, pEdges[j].getI0(), pEdges[j].getI1()); // resolve index ordering and edge_num + //assert(!(i0_A==i1_B && i1_A==i0_B)); + bUnassigned_B = pTriInfos[t].faceNeighbors[edgenum_B[0]] == -1; + if (i0_A[0] == i0_B[0] && i1_A[0] == i1_B[0] && bUnassigned_B) { + bNotFound = false; + } else { + ++j; + } + } + + if (!bNotFound) { + int t2 = pEdges[j].getF(); + pTriInfos[g].faceNeighbors[edgenum_A[0]] = t2; + //assert(pTriInfos[t].FaceNeighbors[edgenum_B]==-1); + pTriInfos[t2].faceNeighbors[edgenum_B[0]] = g; + } + } + } + } + + static void buildNeighborsSlow(TriInfo pTriInfos[], final int piTriListIn[], final int iNrTrianglesIn) { + + for (int f = 0; f < iNrTrianglesIn; f++) { + for (int i = 0; i < 3; i++) { + // if unassigned + if (pTriInfos[f].faceNeighbors[i] == -1) { + final int i0_A = piTriListIn[f * 3 + i]; + final int i1_A = piTriListIn[f * 3 + (i < 2 ? (i + 1) : 0)]; + + // search for a neighbor + boolean bFound = false; + int t = 0, j = 0; + while (!bFound && t < iNrTrianglesIn) { + if (t != f) { + j = 0; + while (!bFound && j < 3) { + // in rev order + final int i1_B = piTriListIn[t * 3 + j]; + final int i0_B = piTriListIn[t * 3 + (j < 2 ? (j + 1) : 0)]; + //assert(!(i0_A==i1_B && i1_A==i0_B)); + if (i0_A == i0_B && i1_A == i1_B) { + bFound = true; + } else { + ++j; + } + } + } + + if (!bFound) { + ++t; + } + } + + // assign neighbors + if (bFound) { + pTriInfos[f].faceNeighbors[i] = t; + //assert(pTriInfos[t].FaceNeighbors[j]==-1); + pTriInfos[t].faceNeighbors[j] = f; + } + } + } + } + } + + static void quickSortEdges(Edge[] pSortBuffer, int iLeft, int iRight, final int channel, long uSeed) { + // early out + Edge sTmp; + final int iElems = iRight - iLeft + 1; + if (iElems < 2) { + return; + } else if (iElems == 2) { + if (pSortBuffer[iLeft].array[channel] > pSortBuffer[iRight].array[channel]) { + sTmp = pSortBuffer[iLeft]; + pSortBuffer[iLeft] = pSortBuffer[iRight]; + pSortBuffer[iRight] = sTmp; + } + return; + } + + // Random + long t = uSeed & 31; + t = (uSeed << t) | (uSeed >> (32 - t)); + uSeed = uSeed + t + 3; + // Random end + + uSeed = uSeed & 0xffffffffL; + + int iL = iLeft; + int iR = iRight; + int n = (iR - iL) + 1; + assert (n >= 0); + int index = (int) (uSeed % n); + + int iMid = pSortBuffer[index + iL].array[channel]; + + do { + while (pSortBuffer[iL].array[channel] < iMid) { + ++iL; + } + while (pSortBuffer[iR].array[channel] > iMid) { + --iR; + } + + if (iL <= iR) { + sTmp = pSortBuffer[iL]; + pSortBuffer[iL] = pSortBuffer[iR]; + pSortBuffer[iR] = sTmp; + ++iL; + --iR; + } + } while (iL <= iR); + + if (iLeft < iR) { + quickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); + } + if (iL < iRight) { + quickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); + } + } + +// resolve ordering and edge number + static void getEdge(int[] i0_out, int[] i1_out, int[] edgenum_out, final int[] indices, final int i0_in, final int i1_in) { + edgenum_out[0] = -1; + + // test if first index is on the edge + if (indices[0] == i0_in || indices[0] == i1_in) { + // test if second index is on the edge + if (indices[1] == i0_in || indices[1] == i1_in) { + edgenum_out[0] = 0; // first edge + i0_out[0] = indices[0]; + i1_out[0] = indices[1]; + } else { + edgenum_out[0] = 2; // third edge + i0_out[0] = indices[2]; + i1_out[0] = indices[0]; + } + } else { + // only second and third index is on the edge + edgenum_out[0] = 1; // second edge + i0_out[0] = indices[1]; + i1_out[0] = indices[2]; + } + } + + static void degenPrologue(TriInfo pTriInfos[], int piTriList_out[], final int iNrTrianglesIn, final int iTotTris) { + + // locate quads with only one good triangle + int t = 0; + while (t < (iTotTris - 1)) { + final int iFO_a = pTriInfos[t].orgFaceNumber; + final int iFO_b = pTriInfos[t + 1].orgFaceNumber; + if (iFO_a == iFO_b) { + // this is a quad + final boolean bIsDeg_a = (pTriInfos[t].flag & MARK_DEGENERATE) != 0; + final boolean bIsDeg_b = (pTriInfos[t + 1].flag & MARK_DEGENERATE) != 0; + //TODO nehon : Check this in detail as this operation is utterly strange + if ((bIsDeg_a ^ bIsDeg_b) != false) { + pTriInfos[t].flag |= QUAD_ONE_DEGEN_TRI; + pTriInfos[t + 1].flag |= QUAD_ONE_DEGEN_TRI; + } + t += 2; + } else { + ++t; + } + } + + // reorder list so all degen triangles are moved to the back + // without reordering the good triangles + int iNextGoodTriangleSearchIndex = 1; + t = 0; + boolean bStillFindingGoodOnes = true; + while (t < iNrTrianglesIn && bStillFindingGoodOnes) { + final boolean bIsGood = (pTriInfos[t].flag & MARK_DEGENERATE) == 0; + if (bIsGood) { + if (iNextGoodTriangleSearchIndex < (t + 2)) { + iNextGoodTriangleSearchIndex = t + 2; + } + } else { + // search for the first good triangle. + boolean bJustADegenerate = true; + while (bJustADegenerate && iNextGoodTriangleSearchIndex < iTotTris) { + final boolean bIsGood2 = (pTriInfos[iNextGoodTriangleSearchIndex].flag & MARK_DEGENERATE) == 0; + if (bIsGood2) { + bJustADegenerate = false; + } else { + ++iNextGoodTriangleSearchIndex; + } + } + + int t0 = t; + int t1 = iNextGoodTriangleSearchIndex; + ++iNextGoodTriangleSearchIndex; + assert (iNextGoodTriangleSearchIndex > (t + 1)); + + // swap triangle t0 and t1 + if (!bJustADegenerate) { + for (int i = 0; i < 3; i++) { + final int index = piTriList_out[t0 * 3 + i]; + piTriList_out[t0 * 3 + i] = piTriList_out[t1 * 3 + i]; + piTriList_out[t1 * 3 + i] = index; + } + { + final TriInfo tri_info = pTriInfos[t0]; + pTriInfos[t0] = pTriInfos[t1]; + pTriInfos[t1] = tri_info; + } + } else { + bStillFindingGoodOnes = false; // this is not supposed to happen + } + } + + if (bStillFindingGoodOnes) { + ++t; + } + } + + assert (bStillFindingGoodOnes); // code will still work. + assert (iNrTrianglesIn == t); + } + + static void DegenEpilogue(TSpace psTspace[], TriInfo pTriInfos[], int piTriListIn[], final MikkTSpaceContext mikkTSpace, final int iNrTrianglesIn, final int iTotTris) { + + // deal with degenerate triangles + // punishment for degenerate triangles is O(N^2) + for (int t = iNrTrianglesIn; t < iTotTris; t++) { + // degenerate triangles on a quad with one good triangle are skipped + // here but processed in the next loop + final boolean bSkip = (pTriInfos[t].flag & QUAD_ONE_DEGEN_TRI) != 0; + + if (!bSkip) { + for (int i = 0; i < 3; i++) { + final int index1 = piTriListIn[t * 3 + i]; + // search through the good triangles + boolean bNotFound = true; + int j = 0; + while (bNotFound && j < (3 * iNrTrianglesIn)) { + final int index2 = piTriListIn[j]; + if (index1 == index2) { + bNotFound = false; + } else { + ++j; + } + } + + if (!bNotFound) { + final int iTri = j / 3; + final int iVert = j % 3; + final int iSrcVert = pTriInfos[iTri].vertNum[iVert]; + final int iSrcOffs = pTriInfos[iTri].tSpacesOffs; + final int iDstVert = pTriInfos[t].vertNum[i]; + final int iDstOffs = pTriInfos[t].tSpacesOffs; + + // copy tspace + psTspace[iDstOffs + iDstVert] = psTspace[iSrcOffs + iSrcVert]; + } + } + } + } + + // deal with degenerate quads with one good triangle + for (int t = 0; t < iNrTrianglesIn; t++) { + // this triangle belongs to a quad where the + // other triangle is degenerate + if ((pTriInfos[t].flag & QUAD_ONE_DEGEN_TRI) != 0) { + + byte[] pV = pTriInfos[t].vertNum; + int iFlag = (1 << pV[0]) | (1 << pV[1]) | (1 << pV[2]); + int iMissingIndex = 0; + if ((iFlag & 2) == 0) { + iMissingIndex = 1; + } else if ((iFlag & 4) == 0) { + iMissingIndex = 2; + } else if ((iFlag & 8) == 0) { + iMissingIndex = 3; + } + + int iOrgF = pTriInfos[t].orgFaceNumber; + Vector3f vDstP = getPosition(mikkTSpace, makeIndex(iOrgF, iMissingIndex)); + boolean bNotFound = true; + int i = 0; + while (bNotFound && i < 3) { + final int iVert = pV[i]; + final Vector3f vSrcP = getPosition(mikkTSpace, makeIndex(iOrgF, iVert)); + if (vSrcP.equals(vDstP)) { + final int iOffs = pTriInfos[t].tSpacesOffs; + psTspace[iOffs + iMissingIndex] = psTspace[iOffs + iVert]; + bNotFound = false; + } else { + ++i; + } + } + assert (!bNotFound); + } + } + + } + + /** + * SubGroup inner class + */ + private static class SubGroup { + int nrFaces; + int[] triMembers; + } + + private static class Group { + int nrFaces; + List faceIndices = new ArrayList(); + int vertexRepresentitive; + boolean orientPreservering; + } + + private static class TriInfo { + + int[] faceNeighbors = new int[3]; + Group[] assignedGroup = new Group[3]; + + // normalized first order face derivatives + Vector3f os = new Vector3f(); + Vector3f ot = new Vector3f(); + float magS, magT; // original magnitudes + + // determines if the current and the next triangle are a quad. + int orgFaceNumber; + int flag, tSpacesOffs; + byte[] vertNum = new byte[4]; + } + + private static class TSpace { + + Vector3f os = new Vector3f(); + float magS; + Vector3f ot = new Vector3f(); + float magT; + int counter; // this is to average back into quads. + boolean orient; + + void set(TSpace ts){ + os.set(ts.os); + magS = ts.magS; + ot.set(ts.ot); + magT = ts.magT; + counter = ts.counter; + orient = ts.orient; + } + } + + private static class TmpVert { + + float vert[] = new float[3]; + int index; + } + + private static class Edge { + + void setI0(int i){ + array[0] = i; + } + + void setI1(int i){ + array[1] = i; + } + + void setF(int i){ + array[2] = i; + } + + int getI0(){ + return array[0]; + } + + int getI1(){ + return array[1]; + } + + int getF(){ + return array[2]; + } + + int[] array = new int[3]; + } + +} 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 0e9e9850b..a0f31aeb0 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 { @@ -213,26 +215,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 { @@ -247,6 +242,7 @@ MaterialDef Phong Lighting { POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows } ForcedRenderState { @@ -265,6 +261,7 @@ MaterialDef Phong Lighting { WorldMatrix ViewProjectionMatrix ViewMatrix + NormalMatrix } Defines { 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..c7edc7951 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.frag @@ -18,6 +18,8 @@ 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; @@ -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/plugins/java/com/jme3/material/plugins/J3MLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/J3MLoader.java index 13a8e0232..65350f29b 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 @@ -169,9 +169,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) { @@ -187,7 +201,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 ")) { @@ -623,6 +637,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 82a08f392..3b09ec310 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert +++ b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.vert @@ -19,6 +19,6 @@ void main(void) #ifdef NUM_BONES Skinning_Compute(modelSpacePos,modelSpaceNormals); #endif - normal = normalize(g_NormalMatrix * modelSpaceNormals); - gl_Position = g_WorldViewProjectionMatrix * modelSpacePos; + normal = normalize(TransformNormal(modelSpaceNormals)); + gl_Position = TransformWorldViewProjection(modelSpacePos); } \ No newline at end of file diff --git a/jme3-examples/build.gradle b/jme3-examples/build.gradle index 09e71997e..371a7aaab 100644 --- a/jme3-examples/build.gradle +++ b/jme3-examples/build.gradle @@ -5,11 +5,15 @@ if (!hasProperty('mainClass')) { } task run(dependsOn: 'build', type:JavaExec) { - main = mainClass - classpath = sourceSets.main.runtimeClasspath - if( assertions == "true" ){ - enableAssertions = true; - } + main = mainClass + classpath = sourceSets.main.runtimeClasspath + if (System.properties['os.name'].toLowerCase().contains('mac')) { + jvmArgs "-XstartOnFirstThread" + jvmArgs "-Djava.awt.headless=true" + } + if( assertions == "true" ){ + enableAssertions = true; + } } dependencies { 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..b1f3a1fe9 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); 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/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-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java index a74b19cdb..0fe8a7b23 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglContext.java @@ -99,24 +99,6 @@ public abstract class LwjglContext implements JmeContext { return Integer.MAX_VALUE; } - protected void loadNatives() { - if (JmeSystem.isLowPermissions()) { - return; - } - - if ("LWJGL".equals(settings.getAudioRenderer())) { - NativeLibraryLoader.loadNativeLibrary("openal-lwjgl3", true); - } - - if (NativeLibraryLoader.isUsingNativeBullet()) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - } - - NativeLibraryLoader.loadNativeLibrary("glfw-lwjgl3", true); - NativeLibraryLoader.loadNativeLibrary("jemalloc-lwjgl3", true); - NativeLibraryLoader.loadNativeLibrary("lwjgl3", true); - } - protected int getNumSamplesToUse() { int samples = 0; if (settings.getSamples() > 1) { @@ -134,6 +116,17 @@ public abstract class LwjglContext implements JmeContext { return samples; } + protected void loadNatives() { + if (JmeSystem.isLowPermissions()) { + return; + } + + if (NativeLibraryLoader.isUsingNativeBullet()) { + NativeLibraryLoader.loadNativeLibrary("bulletjme", true); + } + } + + protected void initContextFirstTime() { final GLCapabilities capabilities = createCapabilities(settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)); diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java index 48cdd1cab..aaa24ce42 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglWindow.java @@ -81,6 +81,8 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { private GLFWWindowSizeCallback windowSizeCallback; private GLFWWindowFocusCallback windowFocusCallback; + private Thread mainThread; + public LwjglWindow(final JmeContext.Type type) { if (!JmeContext.Type.Display.equals(type) && !JmeContext.Type.OffscreenSurface.equals(type) && !JmeContext.Type.Canvas.equals(type)) { throw new IllegalArgumentException("Unsupported type '" + type.name() + "' provided"); @@ -210,7 +212,6 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { @Override public void invoke(final long window, final int focused) { final boolean focus = (focused == GL_TRUE); - if (wasActive != focus) { if (!wasActive) { listener.gainFocus(); @@ -241,10 +242,6 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { glfwSwapInterval(0); } - // Make the window visible - if (Type.Display.equals(type)) { - glfwShowWindow(window); - } glfwShowWindow(window); @@ -286,17 +283,16 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { } } + @Override public void create(boolean waitFor) { if (created.get()) { LOGGER.warning("create() called when display is already created!"); return; } - new Thread(this, THREAD_NAME).start(); - - if (waitFor) { - waitFor(true); - } + // NOTE: this is required for Mac OS X! + mainThread = Thread.currentThread(); + run(); } /** @@ -307,6 +303,7 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { if (!JmeSystem.isLowPermissions()) { // Enable uncaught exception handler only for current thread Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override public void uncaughtException(Thread thread, Throwable thrown) { listener.handleError("Uncaught exception thrown in " + thread.toString(), thrown); if (needClose.get()) { @@ -318,6 +315,8 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { }); } + loadNatives(); + timer = new NanoTimer(); // For canvas, this will create a pbuffer, @@ -434,13 +433,13 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { LOGGER.fine("Display destroyed."); } + @Override public void run() { if (listener == null) { throw new IllegalStateException("SystemListener is not set on context!" + "Must set with JmeContext.setSystemListener()."); } - loadNatives(); LOGGER.log(Level.FINE, "Using LWJGL {0}", Version.getVersion()); if (!initInThread()) { @@ -497,6 +496,11 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { public void destroy(boolean waitFor) { needClose.set(true); + if (mainThread == Thread.currentThread()) { + // Ignore waitFor. + return; + } + if (waitFor) { waitFor(false); } 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..0e3d368d5 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java @@ -0,0 +1,81 @@ +/* + * 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"); + } + 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..584b16f84 --- /dev/null +++ b/jme3-plugins/src/test/java/com/jme3/material/plugin/TestMaterialWrite.java @@ -0,0 +1,115 @@ +/* + * 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 static org.junit.Assert.*; + +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +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 java.util.Scanner; + +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().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/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/sdk/BasicGameTemplate/MANIFEST.MF b/sdk/BasicGameTemplate/MANIFEST.MF deleted file mode 100644 index 7e1ad4bb1..000000000 --- a/sdk/BasicGameTemplate/MANIFEST.MF +++ /dev/null @@ -1 +0,0 @@ -X-Comment: Created with jMonkeyPlatform diff --git a/sdk/BasicGameTemplate/assets/Interface/.keep b/sdk/BasicGameTemplate/assets/Interface/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/sdk/BasicGameTemplate/assets/MatDefs/.keep b/sdk/BasicGameTemplate/assets/MatDefs/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/sdk/BasicGameTemplate/assets/Materials/.keep b/sdk/BasicGameTemplate/assets/Materials/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/sdk/BasicGameTemplate/assets/Models/.keep b/sdk/BasicGameTemplate/assets/Models/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/sdk/BasicGameTemplate/assets/Scenes/.keep b/sdk/BasicGameTemplate/assets/Scenes/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/sdk/BasicGameTemplate/assets/Shaders/.keep b/sdk/BasicGameTemplate/assets/Shaders/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/sdk/BasicGameTemplate/assets/Sounds/.keep b/sdk/BasicGameTemplate/assets/Sounds/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/sdk/BasicGameTemplate/assets/Textures/.keep b/sdk/BasicGameTemplate/assets/Textures/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/sdk/BasicGameTemplate/build.xml b/sdk/BasicGameTemplate/build.xml deleted file mode 100644 index 01bd9fefe..000000000 --- a/sdk/BasicGameTemplate/build.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - Builds, tests, and runs the project BasicGameTemplate. - - - - - diff --git a/sdk/BasicGameTemplate/master-application.jnlp b/sdk/BasicGameTemplate/master-application.jnlp deleted file mode 100644 index 49cbb12f2..000000000 --- a/sdk/BasicGameTemplate/master-application.jnlp +++ /dev/null @@ -1,22 +0,0 @@ - - - ${APPLICATION.TITLE} - ${APPLICATION.VENDOR} - - ${APPLICATION.DESC} - ${APPLICATION.DESC.SHORT} - - - - - - - - - - - - - - - diff --git a/sdk/BasicGameTemplate/nbproject/genfiles.properties b/sdk/BasicGameTemplate/nbproject/genfiles.properties deleted file mode 100644 index 03ee360a8..000000000 --- a/sdk/BasicGameTemplate/nbproject/genfiles.properties +++ /dev/null @@ -1,8 +0,0 @@ -build.xml.data.CRC32=94bf7c61 -build.xml.script.CRC32=79a29eb7 -build.xml.stylesheet.CRC32=958a1d3e@1.32.1.45 -# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. -# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. -nbproject/build-impl.xml.data.CRC32=1ac6e2f9 -nbproject/build-impl.xml.script.CRC32=28b1a2c2 -nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.74.1.48 diff --git a/sdk/BasicGameTemplate/nbproject/project.properties b/sdk/BasicGameTemplate/nbproject/project.properties deleted file mode 100644 index 8f2414b51..000000000 --- a/sdk/BasicGameTemplate/nbproject/project.properties +++ /dev/null @@ -1,92 +0,0 @@ -annotation.processing.enabled=true -annotation.processing.enabled.in.editor=false -annotation.processing.processors.list= -annotation.processing.run.all.processors=true -application.title=MyGame -application.vendor=MyCompany -assets.jar.name=assets.jar -assets.excludes=**/*.j3odata,**/*.mesh,**/*.skeleton,**/*.mesh\.xml,**/*.skeleton\.xml,**/*.scene,**/*.material,**/*.obj,**/*.mtl,**/*.3ds,**/*.dae,**/*.blend,**/*.blend*[0-9] -assets.folder.name=assets -assets.compress=true -build.classes.dir=${build.dir}/classes -build.classes.excludes=**/*.java,**/*.form -# This directory is removed when the project is cleaned: -build.dir=build -build.generated.dir=${build.dir}/generated -build.generated.sources.dir=${build.dir}/generated-sources -# Only compile against the classpath explicitly listed here: -build.sysclasspath=ignore -build.test.classes.dir=${build.dir}/test/classes -build.test.results.dir=${build.dir}/test/results -compile.on.save=true -# Uncomment to specify the preferred debugger connection transport: -#debug.transport=dt_socket -debug.classpath=\ - ${run.classpath} -debug.test.classpath=\ - ${run.test.classpath} -# This directory is removed when the project is cleaned: -dist.dir=dist -dist.jar=${dist.dir}/${application.title}.jar -dist.javadoc.dir=${dist.dir}/javadoc -endorsed.classpath= -excludes= -includes=** -jar.compress=false -javac.classpath=\ - ${libs.jme3-jogg.classpath}:\ - ${libs.jme3-blender.classpath}:\ - ${libs.jme3-networking.classpath}:\ - ${libs.jme3-plugins.classpath}:\ - ${libs.jme3-core.classpath}:\ - ${libs.jme3-desktop.classpath}:\ - ${libs.jme3-lwjgl.classpath}:\ - ${libs.jme3-niftygui.classpath}:\ - ${libs.jme3-effects.classpath}:\ - ${libs.jme3-terrain.classpath}:\ - ${libs.jme3-jbullet.classpath} -# Space-separated list of extra javac options -javac.compilerargs= -javac.deprecation=false -javac.processorpath=\ - ${javac.classpath} -javac.source=1.6 -javac.target=1.6 -javac.test.classpath=\ - ${javac.classpath}:\ - ${build.classes.dir} -javadoc.additionalparam= -javadoc.author=false -javadoc.encoding=${source.encoding} -javadoc.noindex=false -javadoc.nonavbar=false -javadoc.notree=false -javadoc.private=false -javadoc.splitindex=true -javadoc.use=true -javadoc.version=false -javadoc.windowtitle= -jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api" -jnlp.codebase.type=local -jnlp.descriptor=application -jnlp.enabled=false -jnlp.offline-allowed=false -jnlp.signed=false -main.class=mygame.Main -meta.inf.dir=${src.dir}/META-INF -manifest.file=MANIFEST.MF -mkdist.disabled=false -platform.active=default_platform -run.classpath=\ - ${javac.classpath}:\ - ${build.classes.dir}:\ - ${assets.folder.name} -# Space-separated list of JVM arguments used when running the project -# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value -# or test-sys-prop.name=value to set system properties for unit tests): -run.jvmargs= -run.test.classpath=\ - ${javac.test.classpath}:\ - ${build.test.classes.dir} -source.encoding=UTF-8 -src.dir=src diff --git a/sdk/BasicGameTemplate/nbproject/project.xml b/sdk/BasicGameTemplate/nbproject/project.xml deleted file mode 100644 index 3dd779d54..000000000 --- a/sdk/BasicGameTemplate/nbproject/project.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - org.netbeans.modules.java.j2seproject - - - - - - - - BasicGameTemplate - - - - - - - diff --git a/sdk/BasicGameTemplate/src/mygame/Main.java b/sdk/BasicGameTemplate/src/mygame/Main.java deleted file mode 100644 index 91fee9846..000000000 --- a/sdk/BasicGameTemplate/src/mygame/Main.java +++ /dev/null @@ -1,43 +0,0 @@ -package mygame; - -import com.jme3.app.SimpleApplication; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector3f; -import com.jme3.renderer.RenderManager; -import com.jme3.scene.Geometry; -import com.jme3.scene.shape.Box; - -/** - * test - * @author normenhansen - */ -public class Main extends SimpleApplication { - - public static void main(String[] args) { - Main app = new Main(); - app.start(); - } - - @Override - public void simpleInitApp() { - Box b = new Box(1, 1, 1); - Geometry geom = new Geometry("Box", b); - - Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - mat.setColor("Color", ColorRGBA.Blue); - geom.setMaterial(mat); - - rootNode.attachChild(geom); - } - - @Override - public void simpleUpdate(float tpf) { - //TODO: add update code - } - - @Override - public void simpleRender(RenderManager rm) { - //TODO: add render code - } -} diff --git a/sdk/JME3TestsTemplate/build.xml b/sdk/JME3TestsTemplate/build.xml deleted file mode 100644 index d39b505e6..000000000 --- a/sdk/JME3TestsTemplate/build.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - Builds, tests, and runs the project JME3TestsTemplate. - - - diff --git a/sdk/JME3TestsTemplate/nbproject/build-impl.xml b/sdk/JME3TestsTemplate/nbproject/build-impl.xml deleted file mode 100644 index acc4402f2..000000000 --- a/sdk/JME3TestsTemplate/nbproject/build-impl.xml +++ /dev/null @@ -1,880 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must set src.dir - Must set build.dir - Must set dist.dir - Must set build.classes.dir - Must set dist.javadoc.dir - Must set build.test.classes.dir - Must set build.test.results.dir - Must set build.classes.excludes - Must set dist.jar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must set javac.includes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select some files in the IDE or set javac.includes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - To run this application from the command line without Ant, try: - - - - - - - java -cp "${run.classpath.with.dist.jar}" ${main.class} - - - - - - - - - - - - To run this application from the command line without Ant, try: - - java -jar "${dist.jar.resolved}" - - - - - - - - To run this application from the command line without Ant, try: - - java -jar "${dist.jar.resolved}" - - - - - - - - - - - - - - - - - - - Must select one file in the IDE or set run.class - - - - Must select one file in the IDE or set run.class - - - - - - - - - - - - - - - - - - - - - - - Must select one file in the IDE or set debug.class - - - - - Must select one file in the IDE or set debug.class - - - - - Must set fix.includes - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select some files in the IDE or set javac.includes - - - - - - - - - - - - - - - - - - Some tests failed; see details above. - - - - - - - - - Must select some files in the IDE or set test.includes - - - - Some tests failed; see details above. - - - - - Must select one file in the IDE or set test.class - - - - - - - - - - - - - - - - - - - - - - - - - - - Must select one file in the IDE or set applet.url - - - - - - - - - Must select one file in the IDE or set applet.url - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sdk/JME3TestsTemplate/nbproject/genfiles.properties b/sdk/JME3TestsTemplate/nbproject/genfiles.properties deleted file mode 100644 index eca6dafb7..000000000 --- a/sdk/JME3TestsTemplate/nbproject/genfiles.properties +++ /dev/null @@ -1,8 +0,0 @@ -build.xml.data.CRC32=0f706f4a -build.xml.script.CRC32=82b8b23d -build.xml.stylesheet.CRC32=8064a381@1.75.2.48 -# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. -# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. -nbproject/build-impl.xml.data.CRC32=0f706f4a -nbproject/build-impl.xml.script.CRC32=46d1a69a -nbproject/build-impl.xml.stylesheet.CRC32=0ae3a408@1.44.1.45 diff --git a/sdk/JME3TestsTemplate/nbproject/project.properties b/sdk/JME3TestsTemplate/nbproject/project.properties deleted file mode 100644 index 489a9c7d9..000000000 --- a/sdk/JME3TestsTemplate/nbproject/project.properties +++ /dev/null @@ -1,83 +0,0 @@ -application.title=JME3TestsTemplate -application.vendor=jMonkeyEngine -build.classes.dir=${build.dir}/classes -build.classes.excludes=**/*.java,**/*.form -# This directory is removed when the project is cleaned: -build.dir=build -build.generated.dir=${build.dir}/generated -build.generated.sources.dir=${build.dir}/generated-sources -# Only compile against the classpath explicitly listed here: -build.sysclasspath=ignore -build.test.classes.dir=${build.dir}/test/classes -build.test.results.dir=${build.dir}/test/results -# Uncomment to specify the preferred debugger connection transport: -#debug.transport=dt_socket -debug.classpath=\ - ${run.classpath} -debug.test.classpath=\ - ${run.test.classpath} -# This directory is removed when the project is cleaned: -dist.dir=dist -dist.jar=${dist.dir}/JME3TestsTemplate.jar -dist.javadoc.dir=${dist.dir}/javadoc -endorsed.classpath= -excludes= -includes=** -jar.compress=false -javac.classpath=\ - ${libs.jme3-jogg.classpath}:\ - ${libs.jme3-blender.classpath}:\ - ${libs.jme3-networking.classpath}:\ - ${libs.jme3-plugins.classpath}:\ - ${libs.jme3-core.classpath}:\ - ${libs.jme3-desktop.classpath}:\ - ${libs.jme3-lwjgl.classpath}:\ - ${libs.jme3-niftygui.classpath}:\ - ${libs.jme3-effects.classpath}:\ - ${libs.jme3-terrain.classpath}:\ - ${libs.jme3-jbullet.classpath}:\ - ${libs.jme3-test-data.classpath} -# Space-separated list of extra javac options -javac.compilerargs= -javac.deprecation=false -javac.source=1.6 -javac.target=1.6 -javac.test.classpath=\ - ${javac.classpath}:\ - ${build.classes.dir} -javadoc.additionalparam= -javadoc.author=false -javadoc.encoding=${source.encoding} -javadoc.noindex=false -javadoc.nonavbar=false -javadoc.notree=false -javadoc.private=false -javadoc.splitindex=true -javadoc.use=true -javadoc.version=false -javadoc.windowtitle= -jaxbwiz.endorsed.dirs="${netbeans.home}/../ide12/modules/ext/jaxb/api" -jnlp.applet.class=jme3test.awt.TestApplet -jnlp.applet.height=300 -jnlp.applet.width=300 -jnlp.codebase.type=local -jnlp.descriptor=application -jnlp.enabled=false -jnlp.offline-allowed=false -jnlp.signed=false -main.class=jme3test.TestChooser -manifest.file=manifest.mf -meta.inf.dir=${src.dir}/META-INF -platform.active=default_platform -run.classpath=\ - ${javac.classpath}:\ - ${build.classes.dir} -# Space-separated list of JVM arguments used when running the project -# (you may also define separate properties like run-sys-prop.name=value instead of -Dname=value -# or test-sys-prop.name=value to set system properties for unit tests): -run.jvmargs= -run.test.classpath=\ - ${javac.test.classpath}:\ - ${build.test.classes.dir} -source.encoding=UTF-8 -src.dir=src diff --git a/sdk/JME3TestsTemplate/nbproject/project.xml b/sdk/JME3TestsTemplate/nbproject/project.xml deleted file mode 100644 index f6e7a0c99..000000000 --- a/sdk/JME3TestsTemplate/nbproject/project.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - org.netbeans.modules.java.j2seproject - - - JME3TestsTemplate - - - - - - - diff --git a/sdk/JME3TestsTemplateAndroid/MANIFEST.MF b/sdk/JME3TestsTemplateAndroid/MANIFEST.MF deleted file mode 100644 index 7e1ad4bb1..000000000 --- a/sdk/JME3TestsTemplateAndroid/MANIFEST.MF +++ /dev/null @@ -1 +0,0 @@ -X-Comment: Created with jMonkeyPlatform diff --git a/sdk/JME3TestsTemplateAndroid/build.xml b/sdk/JME3TestsTemplateAndroid/build.xml deleted file mode 100644 index c39ff06f2..000000000 --- a/sdk/JME3TestsTemplateAndroid/build.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - Builds, tests, and runs the project BasicGameTemplate. - - - - - diff --git a/sdk/JME3TestsTemplateAndroid/master-application.jnlp b/sdk/JME3TestsTemplateAndroid/master-application.jnlp deleted file mode 100644 index 49cbb12f2..000000000 --- a/sdk/JME3TestsTemplateAndroid/master-application.jnlp +++ /dev/null @@ -1,22 +0,0 @@ - - - ${APPLICATION.TITLE} - ${APPLICATION.VENDOR} - - ${APPLICATION.DESC} - ${APPLICATION.DESC.SHORT} - - - - - - - - - - - - - - - diff --git a/sdk/JME3TestsTemplateAndroid/mobile/AndroidManifest.xml b/sdk/JME3TestsTemplateAndroid/mobile/AndroidManifest.xml deleted file mode 100644 index cddd3676d..000000000 --- a/sdk/JME3TestsTemplateAndroid/mobile/AndroidManifest.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/sdk/JME3TestsTemplateAndroid/mobile/ant.properties b/sdk/JME3TestsTemplateAndroid/mobile/ant.properties deleted file mode 100644 index b0971e891..000000000 --- a/sdk/JME3TestsTemplateAndroid/mobile/ant.properties +++ /dev/null @@ -1,17 +0,0 @@ -# This file is used to override default values used by the Ant build system. -# -# This file must be checked into Version Control Systems, as it is -# integral to the build system of your project. - -# This file is only used by the Ant script. - -# You can use this to override default values such as -# 'source.dir' for the location of your java source folder and -# 'out.dir' for the location of your output folder. - -# You can also use it define how the release builds are signed by declaring -# the following properties: -# 'key.store' for the location of your keystore and -# 'key.alias' for the name of the key to use. -# The password will be asked during the build when you use the 'release' target. - diff --git a/sdk/JME3TestsTemplateAndroid/mobile/build.xml b/sdk/JME3TestsTemplateAndroid/mobile/build.xml deleted file mode 100644 index 601ecade7..000000000 --- a/sdk/JME3TestsTemplateAndroid/mobile/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/sdk/JME3TestsTemplateAndroid/mobile/proguard-project.txt b/sdk/JME3TestsTemplateAndroid/mobile/proguard-project.txt deleted file mode 100644 index b60ae7ea0..000000000 --- a/sdk/JME3TestsTemplateAndroid/mobile/proguard-project.txt +++ /dev/null @@ -1,20 +0,0 @@ -# To enable ProGuard in your project, edit project.properties -# to define the proguard.config property as described in that file. -# -# Add project specific ProGuard rules here. -# By default, the flags in this file are appended to flags specified -# in ${sdk.dir}/tools/proguard/proguard-android.txt -# You can edit the include path and order by changing the ProGuard -# include property in project.properties. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# Add any project specific keep options here: - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} diff --git a/sdk/JME3TestsTemplateAndroid/mobile/project.properties b/sdk/JME3TestsTemplateAndroid/mobile/project.properties deleted file mode 100644 index 85aac5401..000000000 --- a/sdk/JME3TestsTemplateAndroid/mobile/project.properties +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically generated by Android Tools. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file must be checked in Version Control Systems. -# -# To customize properties used by the Ant build system edit -# "ant.properties", and override values to adapt the script to your -# project structure. -# -# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt - -# Project target. -target=android-8 diff --git a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey256.png b/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey256.png deleted file mode 100644 index aa43ad6cf..000000000 Binary files a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey256.png and /dev/null differ diff --git a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey256_9.9.png b/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey256_9.9.png deleted file mode 100644 index 4c90f34a1..000000000 Binary files a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey256_9.9.png and /dev/null differ diff --git a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey512.png b/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey512.png deleted file mode 100644 index c31857454..000000000 Binary files a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey512.png and /dev/null differ diff --git a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey512_9.9.png b/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey512_9.9.png deleted file mode 100644 index 39d45a961..000000000 Binary files a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/monkey512_9.9.png and /dev/null differ diff --git a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/nonselected.png b/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/nonselected.png deleted file mode 100644 index 45cafef25..000000000 Binary files a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/nonselected.png and /dev/null differ diff --git a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/selected.png b/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/selected.png deleted file mode 100644 index ba92fce41..000000000 Binary files a/sdk/JME3TestsTemplateAndroid/mobile/res/drawable/selected.png and /dev/null differ diff --git a/sdk/JME3TestsTemplateAndroid/mobile/res/layout/main.xml b/sdk/JME3TestsTemplateAndroid/mobile/res/layout/main.xml deleted file mode 100644 index 8e4ae707d..000000000 --- a/sdk/JME3TestsTemplateAndroid/mobile/res/layout/main.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/sdk/JME3TestsTemplateAndroid/mobile/res/layout/test_chooser_layout.xml b/sdk/JME3TestsTemplateAndroid/mobile/res/layout/test_chooser_layout.xml deleted file mode 100644 index cfa8c309d..000000000 --- a/sdk/JME3TestsTemplateAndroid/mobile/res/layout/test_chooser_layout.xml +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - - - - - -