/* * 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.asset; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import java.util.Set; import com.jme3.bounding.BoundingVolume; import com.jme3.collision.Collidable; import com.jme3.collision.CollisionResults; import com.jme3.collision.UnsupportedCollisionException; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.light.AmbientLight; import com.jme3.light.Light; import com.jme3.material.Material; import com.jme3.material.RenderState.FaceCullMode; import com.jme3.renderer.Camera; import com.jme3.scene.Node; import com.jme3.scene.SceneGraphVisitor; import com.jme3.scene.Spatial; import com.jme3.scene.plugins.ogre.AnimData; import com.jme3.texture.Texture; /** * Blender key. Contains path of the blender file and its loading properties. * @author Marcin Roguski (Kaelthas) */ public class BlenderKey extends ModelKey { protected static final int DEFAULT_FPS = 25; /** * Animation definitions. The key is the object name that owns the animation. The value is a map between animation * name and its start and stop frames. Blender stores a pointer for animation within object. Therefore one object * can only have one animation at the time. We want to be able to switch between animations for one object so we * need to map the object name to animation names the object will use. */ protected Map<String, Map<String, int[]>> animations; /** * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time * between the frames. */ protected int fps = DEFAULT_FPS; /** Width of generated textures (in pixels). Blender uses 140x140 by default. */ protected int generatedTextureWidth = 140; /** Height of generated textures (in pixels). Blender uses 140x140 by default. */ protected int generatedTextureHeight = 140; /** * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded. */ protected int featuresToLoad = FeaturesToLoad.ALL; /** The root path for all the assets. */ protected String assetRootPath; /** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */ protected boolean fixUpAxis = true; /** * The name of world settings that the importer will use. If not set or specified name does not occur in the file * then the first world settings in the file will be used. */ protected String usedWorld; /** * User's default material that is set fo objects that have no material definition in blender. The default value is * null. If the value is null the importer will use its own default material (gray color - like in blender). */ protected Material defaultMaterial; /** Face cull mode. By default it is disabled. */ protected FaceCullMode faceCullMode = FaceCullMode.Off; /** * Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded. * If set to -1 then the current layer will be loaded. */ protected int layersToLoad = -1; /** * Constructor used by serialization mechanisms. */ public BlenderKey() {} /** * Constructor. Creates a key for the given file name. * @param name * the name (path) of a file */ public BlenderKey(String name) { super(name); } /** * This method adds an animation definition. If a definition already eixists in the key then it is replaced. * @param objectName * the name of animation's owner * @param name * the name of the animation * @param start * the start frame of the animation * @param stop * the stop frame of the animation */ public synchronized void addAnimation(String objectName, String name, int start, int stop) { if(objectName == null) { throw new IllegalArgumentException("Object name cannot be null!"); } if(name == null) { throw new IllegalArgumentException("Animation name cannot be null!"); } if(start > stop) { throw new IllegalArgumentException("Start frame cannot be greater than stop frame!"); } if(animations == null) { animations = new HashMap<String, Map<String, int[]>>(); animations.put(objectName, new HashMap<String, int[]>()); } Map<String, int[]> objectAnimations = animations.get(objectName); if(objectAnimations == null) { objectAnimations = new HashMap<String, int[]>(); animations.put(objectName, objectAnimations); } objectAnimations.put(name, new int[] {start, stop}); } /** * This method returns the animation frames boundaries. * @param objectName * the name of animation's owner * @param name * animation name * @return animation frame boundaries in a table [start, stop] or null if animation of the given name does not * exists */ public int[] getAnimationFrames(String objectName, String name) { Map<String, int[]> objectAnimations = animations == null ? null : animations.get(objectName); int[] frames = objectAnimations == null ? null : objectAnimations.get(name); return frames == null ? null : frames.clone(); } /** * This method returns the animation names for the given object name. * @param objectName * the name of the object * @return an array of animations for this object */ public Set<String> getAnimationNames(String objectName) { Map<String, int[]> objectAnimations = animations == null ? null : animations.get(objectName); return objectAnimations == null ? null : objectAnimations.keySet(); } /** * This method returns the animations map. * @return the animations map */ public Map<String, Map<String, int[]>> getAnimations() { return animations; } /** * This method returns frames per second amount. The default value is BlenderKey.DEFAULT_FPS = 25. * @return the frames per second amount */ public int getFps() { return fps; } /** * This method sets frames per second amount. * @param fps * the frames per second amount */ public void setFps(int fps) { this.fps = fps; } /** * This method sets the width of generated texture (in pixels). By default the value is 140 px. * @param generatedTextureWidth * the width of generated texture */ public void setGeneratedTextureWidth(int generatedTextureWidth) { this.generatedTextureWidth = generatedTextureWidth; } /** * This method returns the width of generated texture (in pixels). By default the value is 140 px. * @return the width of generated texture */ public int getGeneratedTextureWidth() { return generatedTextureWidth; } /** * This method sets the height of generated texture (in pixels). By default the value is 140 px. * @param generatedTextureHeight * the height of generated texture */ public void setGeneratedTextureHeight(int generatedTextureHeight) { this.generatedTextureHeight = generatedTextureHeight; } /** * This method returns the height of generated texture (in pixels). By default the value is 140 px. * @return the height of generated texture */ public int getGeneratedTextureHeight() { return generatedTextureHeight; } /** * This method returns the face cull mode. * @return the face cull mode */ public FaceCullMode getFaceCullMode() { return faceCullMode; } /** * This method sets the face cull mode. * @param faceCullMode * the face cull mode */ public void setFaceCullMode(FaceCullMode faceCullMode) { this.faceCullMode = faceCullMode; } /** * This method sets layers to be loaded. * @param layersToLoad layers to be loaded */ public void setLayersToLoad(int layersToLoad) { this.layersToLoad = layersToLoad; } /** * This method returns layers to be loaded. * @return layers to be loaded */ public int getLayersToLoad() { return layersToLoad; } /** * This method sets the asset root path. * @param assetRootPath * the assets root path */ public void setAssetRootPath(String assetRootPath) { this.assetRootPath = assetRootPath; } /** * This method returns the asset root path. * @return the asset root path */ public String getAssetRootPath() { return assetRootPath; } /** * This method adds features to be loaded. * @param featuresToLoad * bitwise flag of FeaturesToLoad interface values */ public void includeInLoading(int featuresToLoad) { this.featuresToLoad |= featuresToLoad; } /** * This method removes features from being loaded. * @param featuresToLoad * bitwise flag of FeaturesToLoad interface values */ public void excludeFromLoading(int featuresNotToLoad) { this.featuresToLoad &= ~featuresNotToLoad; } /** * This method returns bitwise value of FeaturesToLoad interface value. It describes features that will be loaded by * the blender file loader. * @return features that will be loaded by the blender file loader */ public int getFeaturesToLoad() { return featuresToLoad; } /** * This method creates an object where loading results will be stores. Only those features will be allowed to store * that were specified by features-to-load flag. * @return an object to store loading results */ public LoadingResults prepareLoadingResults() { return new LoadingResults(featuresToLoad); } /** * This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y * is up axis. * @param fixUpAxis * the up axis state variable */ public void setFixUpAxis(boolean fixUpAxis) { this.fixUpAxis = fixUpAxis; } /** * This method returns the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By * default Y is up axis. * @return the up axis state variable */ public boolean isFixUpAxis() { return fixUpAxis; } /** * This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is * not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used * during loading (assumin any exists in the file). * @param usedWorld * the name of the WORLD block used during loading */ public void setUsedWorld(String usedWorld) { this.usedWorld = usedWorld; } /** * This mehtod returns the name of the WORLD data block taht should be used during file loading. * @return the name of the WORLD block used during loading */ public String getUsedWorld() { return usedWorld; } /** * This method sets the default material for objects. * @param defaultMaterial * the default material */ public void setDefaultMaterial(Material defaultMaterial) { this.defaultMaterial = defaultMaterial; } /** * This method returns the default material. * @return the default material */ public Material getDefaultMaterial() { return defaultMaterial; } @Override public void write(JmeExporter e) throws IOException { super.write(e); OutputCapsule oc = e.getCapsule(this); //saving animations oc.write(animations == null ? 0 : animations.size(), "anim-size", 0); if(animations != null) { int objectCounter = 0; for(Entry<String, Map<String, int[]>> animEntry : animations.entrySet()) { oc.write(animEntry.getKey(), "animated-object-" + objectCounter, null); int animsAmount = animEntry.getValue().size(); oc.write(animsAmount, "anims-amount-" + objectCounter, 0); for(Entry<String, int[]> animsEntry : animEntry.getValue().entrySet()) { oc.write(animsEntry.getKey(), "anim-name-" + objectCounter, null); oc.write(animsEntry.getValue(), "anim-frames-" + objectCounter, null); } ++objectCounter; } } //saving the rest of the data oc.write(fps, "fps", DEFAULT_FPS); oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL); oc.write(assetRootPath, "asset-root-path", null); oc.write(fixUpAxis, "fix-up-axis", true); oc.write(usedWorld, "used-world", null); oc.write(defaultMaterial, "default-material", null); oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off); oc.write(layersToLoad, "layers-to-load", -1); } @Override public void read(JmeImporter e) throws IOException { super.read(e); InputCapsule ic = e.getCapsule(this); //reading animations int animSize = ic.readInt("anim-size", 0); if(animSize > 0) { if(animations == null) { animations = new HashMap<String, Map<String, int[]>>(animSize); } else { animations.clear(); } for(int i = 0; i < animSize; ++i) { String objectName = ic.readString("animated-object-" + i, null); int animationsAmount = ic.readInt("anims-amount-" + i, 0); Map<String, int[]> objectAnimations = new HashMap<String, int[]>(animationsAmount); for(int j = 0; j < animationsAmount; ++j) { String animName = ic.readString("anim-name-" + i, null); int[] animFrames = ic.readIntArray("anim-frames-" + i, null); objectAnimations.put(animName, animFrames); } animations.put(objectName, objectAnimations); } } //reading the rest of the data fps = ic.readInt("fps", DEFAULT_FPS); featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL); assetRootPath = ic.readString("asset-root-path", null); fixUpAxis = ic.readBoolean("fix-up-axis", true); usedWorld = ic.readString("used-world", null); defaultMaterial = (Material)ic.readSavable("default-material", null); faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off); layersToLoad = ic.readInt("layers-to=load", -1); } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + (animations == null ? 0 : animations.hashCode()); result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode()); result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode()); result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode()); result = prime * result + featuresToLoad; result = prime * result + (fixUpAxis ? 1231 : 1237); result = prime * result + fps; result = prime * result + generatedTextureHeight; result = prime * result + generatedTextureWidth; result = prime * result + layersToLoad; result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!super.equals(obj)) { return false; } if (this.getClass() != obj.getClass()) { return false; } BlenderKey other = (BlenderKey) obj; if (animations == null) { if (other.animations != null) { return false; } } else if (!animations.equals(other.animations)) { return false; } if (assetRootPath == null) { if (other.assetRootPath != null) { return false; } } else if (!assetRootPath.equals(other.assetRootPath)) { return false; } if (defaultMaterial == null) { if (other.defaultMaterial != null) { return false; } } else if (!defaultMaterial.equals(other.defaultMaterial)) { return false; } if (faceCullMode != other.faceCullMode) { return false; } if (featuresToLoad != other.featuresToLoad) { return false; } if (fixUpAxis != other.fixUpAxis) { return false; } if (fps != other.fps) { return false; } if (generatedTextureHeight != other.generatedTextureHeight) { return false; } if (generatedTextureWidth != other.generatedTextureWidth) { return false; } if (layersToLoad != other.layersToLoad) { return false; } if (usedWorld == null) { if (other.usedWorld != null) { return false; } } else if (!usedWorld.equals(other.usedWorld)) { return false; } return true; } /** * This interface describes the features of the scene that are to be loaded. * @author Marcin Roguski (Kaelthas) */ public static interface FeaturesToLoad { int SCENES = 0x0000FFFF; int OBJECTS = 0x0000000B; int ANIMATIONS = 0x00000004; int MATERIALS = 0x00000003; int TEXTURES = 0x00000001; int CAMERAS = 0x00000020; int LIGHTS = 0x00000010; int ALL = 0xFFFFFFFF; } /** * This class holds the loading results according to the given loading flag. * @author Marcin Roguski (Kaelthas) */ public static class LoadingResults extends Spatial { /** Bitwise mask of features that are to be loaded. */ private final int featuresToLoad; /** The scenes from the file. */ private List<Node> scenes; /** Objects from all scenes. */ private List<Node> objects; /** Materials from all objects. */ private List<Material> materials; /** Textures from all objects. */ private List<Texture> textures; /** Animations of all objects. */ private List<AnimData> animations; /** All cameras from the file. */ private List<Camera> cameras; /** All lights from the file. */ private List<Light> lights; /** * Private constructor prevents users to create an instance of this class from outside the * @param featuresToLoad * bitwise mask of features that are to be loaded * @see FeaturesToLoad FeaturesToLoad */ private LoadingResults(int featuresToLoad) { this.featuresToLoad = featuresToLoad; if((featuresToLoad & FeaturesToLoad.SCENES) != 0) { scenes = new ArrayList<Node>(); } if((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) { objects = new ArrayList<Node>(); if((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) { materials = new ArrayList<Material>(); if((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) { textures = new ArrayList<Texture>(); } } if((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) { animations = new ArrayList<AnimData>(); } } if((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) { cameras = new ArrayList<Camera>(); } if((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) { lights = new ArrayList<Light>(); } } /** * This method returns a bitwise flag describing what features of the blend file will be included in the result. * @return bitwise mask of features that are to be loaded * @see FeaturesToLoad FeaturesToLoad */ public int getLoadedFeatures() { return featuresToLoad; } /** * This method adds a scene to the result set. * @param scene * scene to be added to the result set */ public void addScene(Node scene) { if(scenes != null) { scenes.add(scene); } } /** * This method adds an object to the result set. * @param object * object to be added to the result set */ public void addObject(Node object) { if(objects != null) { objects.add(object); } } /** * This method adds a material to the result set. * @param material * material to be added to the result set */ public void addMaterial(Material material) { if(materials != null) { materials.add(material); } } /** * This method adds a texture to the result set. * @param texture * texture to be added to the result set */ public void addTexture(Texture texture) { if(textures != null) { textures.add(texture); } } /** * This method adds a camera to the result set. * @param camera * camera to be added to the result set */ public void addCamera(Camera camera) { if(cameras != null) { cameras.add(camera); } } /** * This method adds a light to the result set. * @param light * light to be added to the result set */ @Override public void addLight(Light light) { if(lights != null) { lights.add(light); } } /** * This method returns all loaded scenes. * @return all loaded scenes */ public List<Node> getScenes() { return scenes; } /** * This method returns all loaded objects. * @return all loaded objects */ public List<Node> getObjects() { return objects; } /** * This method returns all loaded materials. * @return all loaded materials */ public List<Material> getMaterials() { return materials; } /** * This method returns all loaded textures. * @return all loaded textures */ public List<Texture> getTextures() { return textures; } /** * This method returns all loaded animations. * @return all loaded animations */ public List<AnimData> getAnimations() { return animations; } /** * This method returns all loaded cameras. * @return all loaded cameras */ public List<Camera> getCameras() { return cameras; } /** * This method returns all loaded lights. * @return all loaded lights */ public List<Light> getLights() { return lights; } @Override public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException { return 0; } @Override public void updateModelBound() {} @Override public void setModelBound(BoundingVolume modelBound) {} @Override public int getVertexCount() { return 0; } @Override public int getTriangleCount() { return 0; } @Override public Spatial deepClone() { return null; } @Override public void depthFirstTraversal(SceneGraphVisitor visitor) {} @Override protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {} } /** * The WORLD file block contains various data that could be added to the scene. The contained data includes: ambient * light. * @author Marcin Roguski (Kaelthas) */ public static class WorldData { /** The ambient light. */ private AmbientLight ambientLight; /** * This method returns the world's ambient light. * @return the world's ambient light */ public AmbientLight getAmbientLight() { return ambientLight; } /** * This method sets the world's ambient light. * @param ambientLight * the world's ambient light */ public void setAmbientLight(AmbientLight ambientLight) { this.ambientLight = ambientLight; } } }