diff --git a/engine/src/blender/com/jme3/asset/BlenderKey.java b/engine/src/blender/com/jme3/asset/BlenderKey.java index e39b842d0..dedbbd4d6 100644 --- a/engine/src/blender/com/jme3/asset/BlenderKey.java +++ b/engine/src/blender/com/jme3/asset/BlenderKey.java @@ -63,709 +63,713 @@ import com.jme3.texture.Texture; */ public class BlenderKey extends ModelKey { - protected static final int DEFAULT_FPS = 25; - /** - * 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; - /** - * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded. - */ - protected int featuresToLoad = FeaturesToLoad.ALL; - /** This variable determines if assets that are not linked to the objects should be loaded. */ - protected boolean loadUnlinkedAssets; - /** 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; - /** Generated textures resolution (PPU - Pixels Per Unit). */ - protected int generatedTexturePPU = 128; - /** - * 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.Back; - /** - * 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; - /** A variable that toggles the object custom properties loading. */ - protected boolean loadObjectProperties = true; - /** Maximum texture size. Might be dependant on the graphic card.*/ - protected int maxTextureSize = -1; - /** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */ - protected boolean loadGeneratedTextures; - /** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */ - protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED; - - /** - * 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 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 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 properies loading policy. - * By default the value is true. - * @param loadObjectProperties true to load properties and false to suspend their loading - */ - public void setLoadObjectProperties(boolean loadObjectProperties) { - this.loadObjectProperties = loadObjectProperties; - } - - /** - * @return the current properties loading properties - */ - public boolean isLoadObjectProperties() { - return loadObjectProperties; - } - - /** - * @return maximum texture size (width/height) - */ - public int getMaxTextureSize() { - if(maxTextureSize <= 0) { - try { - maxTextureSize = GL11.glGetInteger(GL11.GL_MAX_TEXTURE_SIZE); - } catch(Exception e) { - //this is in case this method was called before openGL initialization - return 8192; - } - } - return maxTextureSize; - } - - /** - * This method sets the maximum texture size. - * @param maxTextureSize the maximum texture size - */ - public void setMaxTextureSize(int maxTextureSize) { - this.maxTextureSize = maxTextureSize; - } - - /** - * This method sets the flag that toggles the generated textures loading. - * @param loadGeneratedTextures true if generated textures should be loaded and false otherwise - */ - public void setLoadGeneratedTextures(boolean loadGeneratedTextures) { - this.loadGeneratedTextures = loadGeneratedTextures; - } - - /** - * @return tells if the generated textures should be loaded (false is the default value) - */ - public boolean isLoadGeneratedTextures() { - return loadGeneratedTextures; - } - - /** - * 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 featuresNotToLoad - * 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 determines if unlinked assets should be loaded. - * If not then only objects on selected layers will be loaded and their assets if required. - * If yes then all assets will be loaded even if they are on inactive layers or are not linked - * to anything. - * @return true if unlinked assets should be loaded and false otherwise - */ - public boolean isLoadUnlinkedAssets() { - return loadUnlinkedAssets; - } - - /** - * This method sets if unlinked assets should be loaded. - * If not then only objects on selected layers will be loaded and their assets if required. - * If yes then all assets will be loaded even if they are on inactive layers or are not linked - * to anything. - * @param loadUnlinkedAssets - * true if unlinked assets should be loaded and false otherwise - */ - public void setLoadUnlinkedAssets(boolean loadUnlinkedAssets) { - this.loadUnlinkedAssets = loadUnlinkedAssets; - } - - /** - * 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 method sets the generated textures resolution. - * @param generatedTexturePPU the generated textures resolution - */ - public void setGeneratedTexturePPU(int generatedTexturePPU) { - this.generatedTexturePPU = generatedTexturePPU; - } - - /** - * @return the generated textures resolution - */ - public int getGeneratedTexturePPU() { - return generatedTexturePPU; - } - - /** - * @return mipmaps generation method - */ - public MipmapGenerationMethod getMipmapGenerationMethod() { - return mipmapGenerationMethod; - } - - /** - * @param mipmapGenerationMethod - * mipmaps generation method - */ - public void setMipmapGenerationMethod(MipmapGenerationMethod mipmapGenerationMethod) { - this.mipmapGenerationMethod = mipmapGenerationMethod; - } - - /** - * 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); - oc.write(fps, "fps", DEFAULT_FPS); - oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL); - oc.write(loadUnlinkedAssets, "load-unlinked-assets", false); - oc.write(assetRootPath, "asset-root-path", null); - oc.write(fixUpAxis, "fix-up-axis", true); - oc.write(generatedTexturePPU, "generated-texture-ppu", 128); - 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); - oc.write(mipmapGenerationMethod , "mipmap-generation-method", MipmapGenerationMethod.GENERATE_WHEN_NEEDED); - } - - @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule ic = e.getCapsule(this); - fps = ic.readInt("fps", DEFAULT_FPS); - featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL); - loadUnlinkedAssets = ic.readBoolean("load-unlinked-assets", false); - assetRootPath = ic.readString("asset-root-path", null); - fixUpAxis = ic.readBoolean("fix-up-axis", true); - generatedTexturePPU = ic.readInt("generated-texture-ppu", 128); - 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); - mipmapGenerationMethod = ic.readEnum("mipmap-generation-method", MipmapGenerationMethod.class, MipmapGenerationMethod.GENERATE_WHEN_NEEDED); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.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 + generatedTexturePPU; - result = prime * result + layersToLoad; - result = prime * result + (loadUnlinkedAssets ? 1231 : 1237); - 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 (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 (generatedTexturePPU != other.generatedTexturePPU) { - return false; - } - if (layersToLoad != other.layersToLoad) { - return false; - } - if (loadUnlinkedAssets != other.loadUnlinkedAssets) { - return false; - } - if (usedWorld == null) { - if (other.usedWorld != null) { - return false; - } - } else if (!usedWorld.equals(other.usedWorld)) { - return false; - } - return true; - } - - /** - * This enum tells the importer if the mipmaps for textures will be generated by jme. - *
  • NEVER_GENERATE and ALWAYS_GENERATE are quite understandable - *
  • GENERATE_WHEN_NEEDED is an option that checks if the texture had 'Generate mipmaps' option set - * in blender, mipmaps are generated only when the option is set - * @author Marcin Roguski (Kaelthas) - */ - public static enum MipmapGenerationMethod { - NEVER_GENERATE, - ALWAYS_GENERATE, - GENERATE_WHEN_NEEDED; - } - - /** - * 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 scenes; - /** Objects from all scenes. */ - private List objects; - /** Materials from all objects. */ - private List materials; - /** Textures from all objects. */ - private List textures; - /** Animations of all objects. */ - private List animations; - /** All cameras from the file. */ - private Listcameras; - /** All lights from the file. */ - private List 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(); - } - if ((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) { - objects = new ArrayList(); - if ((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) { - materials = new ArrayList(); - if ((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) { - textures = new ArrayList(); - } - } - if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) { - animations = new ArrayList(); - } - } - if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) { - cameras = new ArrayList(); - } - if ((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) { - lights = new ArrayList(); - } - } - - /** - * 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(CameraNode 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 - */ - public void addLight(LightNode light) { - if (lights != null) { - lights.add(light); - } - } - - /** - * This method returns all loaded scenes. - * @return all loaded scenes - */ - public List getScenes() { - return scenes; - } - - /** - * This method returns all loaded objects. - * @return all loaded objects - */ - public List getObjects() { - return objects; - } - - /** - * This method returns all loaded materials. - * @return all loaded materials - */ - public List getMaterials() { - return materials; - } - - /** - * This method returns all loaded textures. - * @return all loaded textures - */ - public List getTextures() { - return textures; - } - - /** - * This method returns all loaded animations. - * @return all loaded animations - */ - public List getAnimations() { - return animations; - } - - /** - * This method returns all loaded cameras. - * @return all loaded cameras - */ - public List getCameras() { - return cameras; - } - - /** - * This method returns all loaded lights. - * @return all loaded lights - */ - public List getLights() { - return lights; - } - - 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 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; - } - } + protected static final int DEFAULT_FPS = 25; + /** + * 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; + /** + * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded. + */ + protected int featuresToLoad = FeaturesToLoad.ALL; + /** This variable determines if assets that are not linked to the objects should be loaded. */ + protected boolean loadUnlinkedAssets; + /** 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; + /** Generated textures resolution (PPU - Pixels Per Unit). */ + protected int generatedTexturePPU = 128; + /** + * 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.Back; + /** + * 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; + /** A variable that toggles the object custom properties loading. */ + protected boolean loadObjectProperties = true; + /** Maximum texture size. Might be dependant on the graphic card. */ + protected int maxTextureSize = -1; + /** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */ + protected boolean loadGeneratedTextures; + /** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */ + protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED; + + /** + * 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 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 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 properies loading policy. + * By default the value is true. + * @param loadObjectProperties + * true to load properties and false to suspend their loading + */ + public void setLoadObjectProperties(boolean loadObjectProperties) { + this.loadObjectProperties = loadObjectProperties; + } + + /** + * @return the current properties loading properties + */ + public boolean isLoadObjectProperties() { + return loadObjectProperties; + } + + /** + * @return maximum texture size (width/height) + */ + public int getMaxTextureSize() { + if (maxTextureSize <= 0) { + try { + maxTextureSize = GL11.glGetInteger(GL11.GL_MAX_TEXTURE_SIZE); + } catch (Exception e) { + // this is in case this method was called before openGL initialization + return 8192; + } + } + return maxTextureSize; + } + + /** + * This method sets the maximum texture size. + * @param maxTextureSize + * the maximum texture size + */ + public void setMaxTextureSize(int maxTextureSize) { + this.maxTextureSize = maxTextureSize; + } + + /** + * This method sets the flag that toggles the generated textures loading. + * @param loadGeneratedTextures + * true if generated textures should be loaded and false otherwise + */ + public void setLoadGeneratedTextures(boolean loadGeneratedTextures) { + this.loadGeneratedTextures = loadGeneratedTextures; + } + + /** + * @return tells if the generated textures should be loaded (false is the default value) + */ + public boolean isLoadGeneratedTextures() { + return loadGeneratedTextures; + } + + /** + * 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 featuresNotToLoad + * 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 determines if unlinked assets should be loaded. + * If not then only objects on selected layers will be loaded and their assets if required. + * If yes then all assets will be loaded even if they are on inactive layers or are not linked + * to anything. + * @return true if unlinked assets should be loaded and false otherwise + */ + public boolean isLoadUnlinkedAssets() { + return loadUnlinkedAssets; + } + + /** + * This method sets if unlinked assets should be loaded. + * If not then only objects on selected layers will be loaded and their assets if required. + * If yes then all assets will be loaded even if they are on inactive layers or are not linked + * to anything. + * @param loadUnlinkedAssets + * true if unlinked assets should be loaded and false otherwise + */ + public void setLoadUnlinkedAssets(boolean loadUnlinkedAssets) { + this.loadUnlinkedAssets = loadUnlinkedAssets; + } + + /** + * 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 method sets the generated textures resolution. + * @param generatedTexturePPU + * the generated textures resolution + */ + public void setGeneratedTexturePPU(int generatedTexturePPU) { + this.generatedTexturePPU = generatedTexturePPU; + } + + /** + * @return the generated textures resolution + */ + public int getGeneratedTexturePPU() { + return generatedTexturePPU; + } + + /** + * @return mipmaps generation method + */ + public MipmapGenerationMethod getMipmapGenerationMethod() { + return mipmapGenerationMethod; + } + + /** + * @param mipmapGenerationMethod + * mipmaps generation method + */ + public void setMipmapGenerationMethod(MipmapGenerationMethod mipmapGenerationMethod) { + this.mipmapGenerationMethod = mipmapGenerationMethod; + } + + /** + * 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); + oc.write(fps, "fps", DEFAULT_FPS); + oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL); + oc.write(loadUnlinkedAssets, "load-unlinked-assets", false); + oc.write(assetRootPath, "asset-root-path", null); + oc.write(fixUpAxis, "fix-up-axis", true); + oc.write(generatedTexturePPU, "generated-texture-ppu", 128); + 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); + oc.write(mipmapGenerationMethod, "mipmap-generation-method", MipmapGenerationMethod.GENERATE_WHEN_NEEDED); + } + + @Override + public void read(JmeImporter e) throws IOException { + super.read(e); + InputCapsule ic = e.getCapsule(this); + fps = ic.readInt("fps", DEFAULT_FPS); + featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL); + loadUnlinkedAssets = ic.readBoolean("load-unlinked-assets", false); + assetRootPath = ic.readString("asset-root-path", null); + fixUpAxis = ic.readBoolean("fix-up-axis", true); + generatedTexturePPU = ic.readInt("generated-texture-ppu", 128); + 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); + mipmapGenerationMethod = ic.readEnum("mipmap-generation-method", MipmapGenerationMethod.class, MipmapGenerationMethod.GENERATE_WHEN_NEEDED); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.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 + generatedTexturePPU; + result = prime * result + layersToLoad; + result = prime * result + (loadUnlinkedAssets ? 1231 : 1237); + 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 (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 (generatedTexturePPU != other.generatedTexturePPU) { + return false; + } + if (layersToLoad != other.layersToLoad) { + return false; + } + if (loadUnlinkedAssets != other.loadUnlinkedAssets) { + return false; + } + if (usedWorld == null) { + if (other.usedWorld != null) { + return false; + } + } else if (!usedWorld.equals(other.usedWorld)) { + return false; + } + return true; + } + + /** + * This enum tells the importer if the mipmaps for textures will be generated by jme.
  • NEVER_GENERATE and ALWAYS_GENERATE are quite understandable
  • GENERATE_WHEN_NEEDED is an option that checks if the texture had 'Generate mipmaps' option set in blender, mipmaps are generated only when the option is set + * @author Marcin Roguski (Kaelthas) + */ + public static enum MipmapGenerationMethod { + NEVER_GENERATE, ALWAYS_GENERATE, GENERATE_WHEN_NEEDED; + } + + /** + * 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 scenes; + /** Objects from all scenes. */ + private List objects; + /** Materials from all objects. */ + private List materials; + /** Textures from all objects. */ + private List textures; + /** Animations of all objects. */ + private List animations; + /** All cameras from the file. */ + private List cameras; + /** All lights from the file. */ + private List 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(); + } + if ((featuresToLoad & FeaturesToLoad.OBJECTS) != 0) { + objects = new ArrayList(); + if ((featuresToLoad & FeaturesToLoad.MATERIALS) != 0) { + materials = new ArrayList(); + if ((featuresToLoad & FeaturesToLoad.TEXTURES) != 0) { + textures = new ArrayList(); + } + } + if ((featuresToLoad & FeaturesToLoad.ANIMATIONS) != 0) { + animations = new ArrayList(); + } + } + if ((featuresToLoad & FeaturesToLoad.CAMERAS) != 0) { + cameras = new ArrayList(); + } + if ((featuresToLoad & FeaturesToLoad.LIGHTS) != 0) { + lights = new ArrayList(); + } + } + + /** + * 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(CameraNode 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 + */ + public void addLight(LightNode light) { + if (lights != null) { + lights.add(light); + } + } + + /** + * This method returns all loaded scenes. + * @return all loaded scenes + */ + public List getScenes() { + return scenes; + } + + /** + * This method returns all loaded objects. + * @return all loaded objects + */ + public List getObjects() { + return objects; + } + + /** + * This method returns all loaded materials. + * @return all loaded materials + */ + public List getMaterials() { + return materials; + } + + /** + * This method returns all loaded textures. + * @return all loaded textures + */ + public List getTextures() { + return textures; + } + + /** + * This method returns all loaded animations. + * @return all loaded animations + */ + public List getAnimations() { + return animations; + } + + /** + * This method returns all loaded cameras. + * @return all loaded cameras + */ + public List getCameras() { + return cameras; + } + + /** + * This method returns all loaded lights. + * @return all loaded lights + */ + public List getLights() { + return lights; + } + + 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 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; + } + } } diff --git a/engine/src/blender/com/jme3/asset/GeneratedTextureKey.java b/engine/src/blender/com/jme3/asset/GeneratedTextureKey.java index 7a6d9a9ed..6bd9017ae 100644 --- a/engine/src/blender/com/jme3/asset/GeneratedTextureKey.java +++ b/engine/src/blender/com/jme3/asset/GeneratedTextureKey.java @@ -36,7 +36,7 @@ package com.jme3.asset; * This key is mostly used to distinguish between textures that are loaded from * the given assets and those being generated automatically. Every generated * texture will have this kind of key attached. - * + * * @author Marcin Roguski (Kaelthas) */ public class GeneratedTextureKey extends TextureKey { @@ -44,8 +44,9 @@ public class GeneratedTextureKey extends TextureKey { /** * Constructor. Stores the name. Extension and folder name are empty * strings. - * - * @param name the name of the texture + * + * @param name + * the name of the texture */ public GeneratedTextureKey(String name) { super(name); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java index d9ccf91c5..9e52e7ba3 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java @@ -50,116 +50,116 @@ import com.jme3.scene.plugins.blender.objects.Properties; */ public abstract class AbstractBlenderHelper { - /** The version of the blend file. */ - protected final int blenderVersion; - /** This variable indicates if the Y asxis is the UP axis or not. */ - protected boolean fixUpAxis; - /** Quaternion used to rotate data when Y is up axis. */ - protected Quaternion upAxisRotationQuaternion; - - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender - * versions. - * @param blenderVersion - * the version read from the blend file - * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not - */ - public AbstractBlenderHelper(String blenderVersion, boolean fixUpAxis) { - this.blenderVersion = Integer.parseInt(blenderVersion); - this.fixUpAxis = fixUpAxis; - if(fixUpAxis) { - upAxisRotationQuaternion = new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0); - } - } - - /** - * This method clears the state of the helper so that it can be used for different calculations of another feature. - */ - public void clearState() {} + /** The version of the blend file. */ + protected final int blenderVersion; + /** This variable indicates if the Y asxis is the UP axis or not. */ + protected boolean fixUpAxis; + /** Quaternion used to rotate data when Y is up axis. */ + protected Quaternion upAxisRotationQuaternion; - /** - * This method should be used to check if the text is blank. Avoid using text.trim().length()==0. This causes that more strings are - * being created and stored in the memory. It can be unwise especially inside loops. - * @param text - * the text to be checked - * @return true if the text is blank and false otherwise - */ - protected boolean isBlank(String text) { - if (text != null) { - for (int i = 0; i < text.length(); ++i) { - if (!Character.isWhitespace(text.charAt(i))) { - return false; - } - } - } - return true; - } + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender + * versions. + * @param blenderVersion + * the version read from the blend file + * @param fixUpAxis + * a variable that indicates if the Y asxis is the UP axis or not + */ + public AbstractBlenderHelper(String blenderVersion, boolean fixUpAxis) { + this.blenderVersion = Integer.parseInt(blenderVersion); + this.fixUpAxis = fixUpAxis; + if (fixUpAxis) { + upAxisRotationQuaternion = new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0); + } + } - /** - * This method loads the properties if they are available and defined for the structure. - * @param structure - * the structure we read the properties from - * @param blenderContext - * the blender context - * @return loaded properties or null if they are not available - * @throws BlenderFileException - * an exception is thrown when the blend file is somehow corrupted - */ - protected Properties loadProperties(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - Properties properties = null; - Structure id = (Structure) structure.getFieldValue("ID"); - if (id != null) { - Pointer pProperties = (Pointer) id.getFieldValue("properties"); - if (pProperties.isNotNull()) { - Structure propertiesStructure = pProperties.fetchData(blenderContext.getInputStream()).get(0); - properties = new Properties(); - properties.load(propertiesStructure, blenderContext); - } - } - return properties; - } - - /** - * The method applies properties to the given spatial. The Properties - * instance cannot be directly applied because the end-user might not have - * the blender plugin jar file and thus receive ClassNotFoundException. The - * values are set by name instead. - * - * @param spatial - * the spatial that is to have properties applied - * @param properties - * the properties to be applied - */ - protected void applyProperties(Spatial spatial, Properties properties) { - List propertyNames = properties.getSubPropertiesNames(); - if(propertyNames != null && propertyNames.size() > 0) { - for(String propertyName : propertyNames) { - Object value = properties.findValue(propertyName); - if(value instanceof Savable || value instanceof Boolean || value instanceof String || - value instanceof Float || value instanceof Integer || value instanceof Long) { - spatial.setUserData(propertyName, value); - } else if(value instanceof Double) { - spatial.setUserData(propertyName, ((Double) value).floatValue()); - } else if(value instanceof int[]) { - spatial.setUserData(propertyName, Arrays.toString((int[])value)); - } else if(value instanceof float[]) { - spatial.setUserData(propertyName, Arrays.toString((float[])value)); - } else if(value instanceof double[]) { - spatial.setUserData(propertyName, Arrays.toString((double[])value)); - } - } - } - } - - /** - * This method analyzes the given structure and the data contained within - * blender context and decides if the feature should be loaded. - * @param structure - * structure to be analyzed - * @param blenderContext - * the blender context - * @return true if the feature should be loaded and false otherwise - */ - public abstract boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext); + /** + * This method clears the state of the helper so that it can be used for different calculations of another feature. + */ + public void clearState() { + } + + /** + * This method should be used to check if the text is blank. Avoid using text.trim().length()==0. This causes that more strings are + * being created and stored in the memory. It can be unwise especially inside loops. + * @param text + * the text to be checked + * @return true if the text is blank and false otherwise + */ + protected boolean isBlank(String text) { + if (text != null) { + for (int i = 0; i < text.length(); ++i) { + if (!Character.isWhitespace(text.charAt(i))) { + return false; + } + } + } + return true; + } + + /** + * This method loads the properties if they are available and defined for the structure. + * @param structure + * the structure we read the properties from + * @param blenderContext + * the blender context + * @return loaded properties or null if they are not available + * @throws BlenderFileException + * an exception is thrown when the blend file is somehow corrupted + */ + protected Properties loadProperties(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + Properties properties = null; + Structure id = (Structure) structure.getFieldValue("ID"); + if (id != null) { + Pointer pProperties = (Pointer) id.getFieldValue("properties"); + if (pProperties.isNotNull()) { + Structure propertiesStructure = pProperties.fetchData(blenderContext.getInputStream()).get(0); + properties = new Properties(); + properties.load(propertiesStructure, blenderContext); + } + } + return properties; + } + + /** + * The method applies properties to the given spatial. The Properties + * instance cannot be directly applied because the end-user might not have + * the blender plugin jar file and thus receive ClassNotFoundException. The + * values are set by name instead. + * + * @param spatial + * the spatial that is to have properties applied + * @param properties + * the properties to be applied + */ + protected void applyProperties(Spatial spatial, Properties properties) { + List propertyNames = properties.getSubPropertiesNames(); + if (propertyNames != null && propertyNames.size() > 0) { + for (String propertyName : propertyNames) { + Object value = properties.findValue(propertyName); + if (value instanceof Savable || value instanceof Boolean || value instanceof String || value instanceof Float || value instanceof Integer || value instanceof Long) { + spatial.setUserData(propertyName, value); + } else if (value instanceof Double) { + spatial.setUserData(propertyName, ((Double) value).floatValue()); + } else if (value instanceof int[]) { + spatial.setUserData(propertyName, Arrays.toString((int[]) value)); + } else if (value instanceof float[]) { + spatial.setUserData(propertyName, Arrays.toString((float[]) value)); + } else if (value instanceof double[]) { + spatial.setUserData(propertyName, Arrays.toString((double[]) value)); + } + } + } + } + + /** + * This method analyzes the given structure and the data contained within + * blender context and decides if the feature should be loaded. + * @param structure + * structure to be analyzed + * @param blenderContext + * the blender context + * @return true if the feature should be loaded and false otherwise + */ + public abstract boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext); } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java index b1953eaaa..8b6323f01 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/AbstractBlenderLoader.java @@ -57,134 +57,134 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; * This class converts blender file blocks into jMonkeyEngine data structures. * @author Marcin Roguski (Kaelthas) */ -/* package */ abstract class AbstractBlenderLoader implements AssetLoader { - private static final Logger LOGGER = Logger.getLogger(AbstractBlenderLoader.class.getName()); - - protected BlenderContext blenderContext; +/* package */abstract class AbstractBlenderLoader implements AssetLoader { + private static final Logger LOGGER = Logger.getLogger(AbstractBlenderLoader.class.getName()); - /** - * This method converts the given structure to a scene node. - * @param structure - * structure of a scene - * @return scene's node - */ - public Node toScene(Structure structure) { - if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.SCENES) == 0) { - return null; - } - Node result = new Node(structure.getName()); - try { - List base = ((Structure)structure.getFieldValue("base")).evaluateListBase(blenderContext); - for(Structure b : base) { - Pointer pObject = (Pointer) b.getFieldValue("object"); - if(pObject.isNotNull()) { - Structure objectStructure = pObject.fetchData(blenderContext.getInputStream()).get(0); - Object object = this.toObject(objectStructure); - if(object instanceof LightNode && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { - result.addLight(((LightNode)object).getLight()); - result.attachChild((LightNode) object); - } else if (object instanceof Node && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { - LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); - if (((Node) object).getParent() == null) { - result.attachChild((Spatial) object); + protected BlenderContext blenderContext; + + /** + * This method converts the given structure to a scene node. + * @param structure + * structure of a scene + * @return scene's node + */ + public Node toScene(Structure structure) { + if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.SCENES) == 0) { + return null; + } + Node result = new Node(structure.getName()); + try { + List base = ((Structure) structure.getFieldValue("base")).evaluateListBase(blenderContext); + for (Structure b : base) { + Pointer pObject = (Pointer) b.getFieldValue("object"); + if (pObject.isNotNull()) { + Structure objectStructure = pObject.fetchData(blenderContext.getInputStream()).get(0); + Object object = this.toObject(objectStructure); + if (object instanceof LightNode && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { + result.addLight(((LightNode) object).getLight()); + result.attachChild((LightNode) object); + } else if (object instanceof Node && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { + LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); + if (((Node) object).getParent() == null) { + result.attachChild((Spatial) object); } - } - } - } - } catch (BlenderFileException e) { - LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); - } - return result; - } + } + } + } + } catch (BlenderFileException e) { + LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); + } + return result; + } - /** - * This method converts the given structure to a camera. - * @param structure - * structure of a camera - * @return camera's node - */ - public CameraNode toCamera(Structure structure) throws BlenderFileException { - CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class); - if (cameraHelper.shouldBeLoaded(structure, blenderContext)) { - return cameraHelper.toCamera(structure, blenderContext); - } - return null; - } + /** + * This method converts the given structure to a camera. + * @param structure + * structure of a camera + * @return camera's node + */ + public CameraNode toCamera(Structure structure) throws BlenderFileException { + CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class); + if (cameraHelper.shouldBeLoaded(structure, blenderContext)) { + return cameraHelper.toCamera(structure, blenderContext); + } + return null; + } - /** - * This method converts the given structure to a light. - * @param structure - * structure of a light - * @return light's node - */ - public LightNode toLight(Structure structure) throws BlenderFileException { - LightHelper lightHelper = blenderContext.getHelper(LightHelper.class); - if (lightHelper.shouldBeLoaded(structure, blenderContext)) { - return lightHelper.toLight(structure, blenderContext); - } - return null; - } + /** + * This method converts the given structure to a light. + * @param structure + * structure of a light + * @return light's node + */ + public LightNode toLight(Structure structure) throws BlenderFileException { + LightHelper lightHelper = blenderContext.getHelper(LightHelper.class); + if (lightHelper.shouldBeLoaded(structure, blenderContext)) { + return lightHelper.toLight(structure, blenderContext); + } + return null; + } - /** - * This method converts the given structure to a node. - * @param structure - * structure of an object - * @return object's node - */ - public Object toObject(Structure structure) throws BlenderFileException { - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - if (objectHelper.shouldBeLoaded(structure, blenderContext)) { - return objectHelper.toObject(structure, blenderContext); - } - return null; - } + /** + * This method converts the given structure to a node. + * @param structure + * structure of an object + * @return object's node + */ + public Object toObject(Structure structure) throws BlenderFileException { + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + if (objectHelper.shouldBeLoaded(structure, blenderContext)) { + return objectHelper.toObject(structure, blenderContext); + } + return null; + } - /** - * This method converts the given structure to a list of geometries. - * @param structure - * structure of a mesh - * @return list of geometries - */ - public List toMesh(Structure structure) throws BlenderFileException { - MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); - if (meshHelper.shouldBeLoaded(structure, blenderContext)) { - return meshHelper.toMesh(structure, blenderContext); - } - return null; - } + /** + * This method converts the given structure to a list of geometries. + * @param structure + * structure of a mesh + * @return list of geometries + */ + public List toMesh(Structure structure) throws BlenderFileException { + MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); + if (meshHelper.shouldBeLoaded(structure, blenderContext)) { + return meshHelper.toMesh(structure, blenderContext); + } + return null; + } -// /** -// * This method converts the given structure to a material. -// * @param structure -// * structure of a material -// * @return material's node -// */ -// public Material toMaterial(Structure structure) throws BlenderFileException { -// MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); -// if (materialHelper.shouldBeLoaded(structure, blenderContext)) { -// return materialHelper.toMaterial(structure, blenderContext); -// } -// return null; -// } + // /** + // * This method converts the given structure to a material. + // * @param structure + // * structure of a material + // * @return material's node + // */ + // public Material toMaterial(Structure structure) throws BlenderFileException { + // MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + // if (materialHelper.shouldBeLoaded(structure, blenderContext)) { + // return materialHelper.toMaterial(structure, blenderContext); + // } + // return null; + // } - /** - * This method returns the data read from the WORLD file block. The block contains data that can be stored as - * separate jme features and therefore cannot be returned as a single jME scene feature. - * @param structure - * the structure with WORLD block data - * @return data read from the WORLD block that can be added to the scene - */ - public WorldData toWorldData(Structure structure) { - WorldData result = new WorldData(); + /** + * This method returns the data read from the WORLD file block. The block contains data that can be stored as + * separate jme features and therefore cannot be returned as a single jME scene feature. + * @param structure + * the structure with WORLD block data + * @return data read from the WORLD block that can be added to the scene + */ + public WorldData toWorldData(Structure structure) { + WorldData result = new WorldData(); - // reading ambient light - AmbientLight ambientLight = new AmbientLight(); - float ambr = ((Number) structure.getFieldValue("ambr")).floatValue(); - float ambg = ((Number) structure.getFieldValue("ambg")).floatValue(); - float ambb = ((Number) structure.getFieldValue("ambb")).floatValue(); - ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f)); - result.setAmbientLight(ambientLight); + // reading ambient light + AmbientLight ambientLight = new AmbientLight(); + float ambr = ((Number) structure.getFieldValue("ambr")).floatValue(); + float ambg = ((Number) structure.getFieldValue("ambg")).floatValue(); + float ambb = ((Number) structure.getFieldValue("ambb")).floatValue(); + ambientLight.setColor(new ColorRGBA(ambr, ambg, ambb, 0.0f)); + result.setAmbientLight(ambientLight); - return result; - } + return result; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java index fcdb423c7..a786e8ad4 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java @@ -65,625 +65,626 @@ import com.jme3.scene.plugins.ogre.AnimData; * @author Marcin Roguski (Kaelthas) */ public class BlenderContext { - private static final Logger LOGGER = Logger.getLogger(BlenderContext.class.getName()); - - /** The blender file version. */ - private int blenderVersion; - /** The blender key. */ - private BlenderKey blenderKey; - /** The header of the file block. */ - private DnaBlockData dnaBlockData; - /** The scene structure. */ - private Structure sceneStructure; - /** The input stream of the blend file. */ - private BlenderInputStream inputStream; - /** The asset manager. */ - private AssetManager assetManager; - /** - * A map containing the file block headers. The key is the old pointer - * address. - */ - private Map fileBlockHeadersByOma = new HashMap(); - /** A map containing the file block headers. The key is the block code. */ - private Map> fileBlockHeadersByCode = new HashMap>(); - /** - * This map stores the loaded features by their old memory address. The - * first object in the value table is the loaded structure and the second - - * the structure already converted into proper data. - */ - private Map loadedFeatures = new HashMap(); - /** - * This map stores the loaded features by their name. Only features with ID - * structure can be stored here. The first object in the value table is the - * loaded structure and the second - the structure already converted into - * proper data. - */ - private Map loadedFeaturesByName = new HashMap(); - /** A stack that hold the parent structure of currently loaded feature. */ - private Stack parentStack = new Stack(); - /** - * A map storing loaded ipos. The key is the ipo's owner old memory address - * and the value is the ipo. - */ - private Map loadedIpos = new HashMap(); - /** A list of modifiers for the specified object. */ - protected Map> modifiers = new HashMap>(); - /** A list of constraints for the specified object. */ - protected Map> constraints = new HashMap>(); - /** Anim data loaded for features. */ - private Map animData = new HashMap(); - /** Loaded skeletons. */ - private Map skeletons = new HashMap(); - /** A map of mesh contexts. */ - protected Map meshContexts = new HashMap(); - /** A map of bone contexts. */ - protected Map boneContexts = new HashMap(); - /** A map og helpers that perform loading. */ - private Map helpers = new HashMap(); - - /** - * This method sets the blender file version. - * - * @param blenderVersion - * the blender file version - */ - public void setBlenderVersion(String blenderVersion) { - this.blenderVersion = Integer.parseInt(blenderVersion); - } - - /** - * @return the blender file version - */ - public int getBlenderVersion() { - return blenderVersion; - } - - /** - * This method sets the blender key. - * - * @param blenderKey - * the blender key - */ - public void setBlenderKey(BlenderKey blenderKey) { - this.blenderKey = blenderKey; - } - - /** - * This method returns the blender key. - * - * @return the blender key - */ - public BlenderKey getBlenderKey() { - return blenderKey; - } - - /** - * This method sets the dna block data. - * - * @param dnaBlockData - * the dna block data - */ - public void setBlockData(DnaBlockData dnaBlockData) { - this.dnaBlockData = dnaBlockData; - } - - /** - * This method returns the dna block data. - * - * @return the dna block data - */ - public DnaBlockData getDnaBlockData() { - return dnaBlockData; - } - /** - * This method sets the scene structure data. - * - * @param sceneStructure - * the scene structure data - */ - public void setSceneStructure(Structure sceneStructure) { - this.sceneStructure = sceneStructure; - } - - /** - * This method returns the scene structure data. - * - * @return the scene structure data - */ - public Structure getSceneStructure() { - return sceneStructure; - } - - /** - * This method returns the asset manager. - * - * @return the asset manager - */ - public AssetManager getAssetManager() { - return assetManager; - } - - /** - * This method sets the asset manager. - * - * @param assetManager - * the asset manager - */ - public void setAssetManager(AssetManager assetManager) { - this.assetManager = assetManager; - } - - /** - * This method returns the input stream of the blend file. - * - * @return the input stream of the blend file - */ - public BlenderInputStream getInputStream() { - return inputStream; - } - - /** - * This method sets the input stream of the blend file. - * - * @param inputStream - * the input stream of the blend file - */ - public void setInputStream(BlenderInputStream inputStream) { - this.inputStream = inputStream; - } - - /** - * This method adds a file block header to the map. Its old memory address - * is the key. - * - * @param oldMemoryAddress - * the address of the block header - * @param fileBlockHeader - * the block header to store - */ - public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) { - fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader); - List headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode())); - if (headers == null) { - headers = new ArrayList(); - fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers); - } - headers.add(fileBlockHeader); - } - - /** - * This method returns the block header of a given memory address. If the - * header is not present then null is returned. - * - * @param oldMemoryAddress - * the address of the block header - * @return loaded header or null if it was not yet loaded - */ - public FileBlockHeader getFileBlock(Long oldMemoryAddress) { - return fileBlockHeadersByOma.get(oldMemoryAddress); - } - - /** - * This method returns a list of file blocks' headers of a specified code. - * - * @param code - * the code of file blocks - * @return a list of file blocks' headers of a specified code - */ - public List getFileBlocks(Integer code) { - return fileBlockHeadersByCode.get(code); - } - - /** - * This method clears the saved block headers stored in the features map. - */ - public void clearFileBlocks() { - fileBlockHeadersByOma.clear(); - fileBlockHeadersByCode.clear(); - } - - /** - * This method adds a helper instance to the helpers' map. - * - * @param - * the type of the helper - * @param clazz - * helper's class definition - * @param helper - * the helper instance - */ - public void putHelper(Class clazz, AbstractBlenderHelper helper) { - helpers.put(clazz.getSimpleName(), helper); - } - - @SuppressWarnings("unchecked") - public T getHelper(Class clazz) { - return (T) helpers.get(clazz.getSimpleName()); - } - - /** - * This method adds a loaded feature to the map. The key is its unique old - * memory address. - * - * @param oldMemoryAddress - * the address of the feature - * @param featureName - * the name of the feature - * @param structure - * the filled structure of the feature - * @param feature - * the feature we want to store - */ - public void addLoadedFeatures(Long oldMemoryAddress, String featureName, Structure structure, Object feature) { - if (oldMemoryAddress == null || structure == null || feature == null) { - throw new IllegalArgumentException("One of the given arguments is null!"); - } - Object[] storedData = new Object[] { structure, feature }; - loadedFeatures.put(oldMemoryAddress, storedData); - if (featureName != null) { - loadedFeaturesByName.put(featureName, storedData); - } - } - - /** - * This method returns the feature of a given memory address. If the feature - * is not yet loaded then null is returned. - * - * @param oldMemoryAddress - * the address of the feature - * @param loadedFeatureDataType - * the type of data we want to retreive it can be either filled - * structure or already converted feature - * @return loaded feature or null if it was not yet loaded - */ - public Object getLoadedFeature(Long oldMemoryAddress, LoadedFeatureDataType loadedFeatureDataType) { - Object[] result = loadedFeatures.get(oldMemoryAddress); - if (result != null) { - return result[loadedFeatureDataType.getIndex()]; - } - return null; - } - - /** - * This method returns the feature of a given name. If the feature is not - * yet loaded then null is returned. - * - * @param featureName - * the name of the feature - * @param loadedFeatureDataType - * the type of data we want to retreive it can be either filled - * structure or already converted feature - * @return loaded feature or null if it was not yet loaded - */ - public Object getLoadedFeature(String featureName, LoadedFeatureDataType loadedFeatureDataType) { - Object[] result = loadedFeaturesByName.get(featureName); - if (result != null) { - return result[loadedFeatureDataType.getIndex()]; - } - return null; - } - - /** - * This method clears the saved features stored in the features map. - */ - public void clearLoadedFeatures() { - loadedFeatures.clear(); - } - - /** - * This method adds the structure to the parent stack. - * - * @param parent - * the structure to be added to the stack - */ - public void pushParent(Structure parent) { - parentStack.push(parent); - } - - /** - * This method removes the structure from the top of the parent's stack. - * - * @return the structure that was removed from the stack - */ - public Structure popParent() { - try { - return parentStack.pop(); - } catch (EmptyStackException e) { - return null; - } - } - - /** - * This method retreives the structure at the top of the parent's stack but - * does not remove it. - * - * @return the structure from the top of the stack - */ - public Structure peekParent() { - try { - return parentStack.peek(); - } catch (EmptyStackException e) { - return null; - } - } - - /** - * This method adds new ipo curve for the feature. - * - * @param ownerOMA - * the OMA of blender feature that owns the ipo - * @param ipo - * the ipo to be added - */ - public void addIpo(Long ownerOMA, Ipo ipo) { - loadedIpos.put(ownerOMA, ipo); - } - - /** - * This method removes the ipo curve from the feature. - * - * @param ownerOma - * the OMA of blender feature that owns the ipo - */ - public Ipo removeIpo(Long ownerOma) { - return loadedIpos.remove(ownerOma); - } - - /** - * This method returns the ipo curve of the feature. - * - * @param ownerOMA - * the OMA of blender feature that owns the ipo - */ - public Ipo getIpo(Long ownerOMA) { - return loadedIpos.get(ownerOMA); - } - - /** - * This method adds a new modifier to the list. - * - * @param ownerOMA - * the owner's old memory address - * @param modifier - * the object's modifier - */ - public void addModifier(Long ownerOMA, Modifier modifier) { - List objectModifiers = this.modifiers.get(ownerOMA); - if (objectModifiers == null) { - objectModifiers = new ArrayList(); - this.modifiers.put(ownerOMA, objectModifiers); - } - objectModifiers.add(modifier); - } - - /** - * This method returns modifiers for the object specified by its old memory - * address and the modifier type. If no modifiers are found - empty list is - * returned. If the type is null - all modifiers for the object are - * returned. - * - * @param objectOMA - * object's old memory address - * @param type - * the type of the modifier - * @return the list of object's modifiers - */ - public List getModifiers(Long objectOMA, String type) { - List result = new ArrayList(); - List readModifiers = modifiers.get(objectOMA); - if (readModifiers != null && readModifiers.size() > 0) { - for (Modifier modifier : readModifiers) { - if (type == null || type.isEmpty() || modifier.getType().equals(type)) { - result.add(modifier); - } - } - } - return result; - } - - /** - * This method adds a new modifier to the list. - * - * @param ownerOMA - * the owner's old memory address - * @param constraints - * the object's constraints - */ - public void addConstraints(Long ownerOMA, List constraints) { - List objectConstraints = this.constraints.get(ownerOMA); - if (objectConstraints == null) { - objectConstraints = new ArrayList(); - this.constraints.put(ownerOMA, objectConstraints); - } - objectConstraints.addAll(constraints); - } - - /** - * This method returns constraints for the object specified by its old - * memory address. If no modifiers are found - null is returned. - * - * @param objectOMA - * object's old memory address - * @return the list of object's modifiers or null - */ - public List getConstraints(Long objectOMA) { - return objectOMA == null ? null : constraints.get(objectOMA); - } - - /** - * @return all available constraints - */ - public List getAllConstraints() { - List result = new ArrayList(); - for(Entry> entry : constraints.entrySet()) { - result.addAll(entry.getValue()); - } - return result; - } - - /** - * This method sets the anim data for the specified OMA of its owner. - * - * @param ownerOMA - * the owner's old memory address - * @param animData - * the animation data for the feature specified by ownerOMA - */ - public void setAnimData(Long ownerOMA, AnimData animData) { - this.animData.put(ownerOMA, animData); - } - - /** - * This method returns the animation data for the specified owner. - * - * @param ownerOMA - * the old memory address of the animation data owner - * @return the animation data or null if none exists - */ - public AnimData getAnimData(Long ownerOMA) { - return this.animData.get(ownerOMA); - } - - /** - * This method sets the skeleton for the specified OMA of its owner. - * - * @param skeletonOMA - * the skeleton's old memory address - * @param skeleton - * the skeleton specified by the given OMA - */ - public void setSkeleton(Long skeletonOMA, Skeleton skeleton) { - this.skeletons.put(skeletonOMA, skeleton); - } - - /** - * This method returns the skeleton for the specified OMA of its owner. - * - * @param skeletonOMA - * the skeleton's old memory address - * @return the skeleton specified by the given OMA - */ - public Skeleton getSkeleton(Long skeletonOMA) { - return this.skeletons.get(skeletonOMA); - } - - /** - * This method sets the mesh context for the given mesh old memory address. - * If the context is already set it will be replaced. - * - * @param meshOMA - * the mesh's old memory address - * @param meshContext - * the mesh's context - */ - public void setMeshContext(Long meshOMA, MeshContext meshContext) { - this.meshContexts.put(meshOMA, meshContext); - } - - /** - * This method returns the mesh context for the given mesh old memory - * address. If no context exists then null is returned. - * - * @param meshOMA - * the mesh's old memory address - * @return mesh's context - */ - public MeshContext getMeshContext(Long meshOMA) { - return this.meshContexts.get(meshOMA); - } - - /** - * This method sets the bone context for the given bone old memory address. - * If the context is already set it will be replaced. - * - * @param boneOMA - * the bone's old memory address - * @param boneContext - * the bones's context - */ - public void setBoneContext(Long boneOMA, BoneContext boneContext) { - this.boneContexts.put(boneOMA, boneContext); - } - - /** - * This method returns the bone context for the given bone old memory - * address. If no context exists then null is returned. - * - * @param boneOMA - * the bone's old memory address - * @return bone's context - */ - public BoneContext getBoneContext(Long boneOMA) { - return boneContexts.get(boneOMA); - } - - /** - * Returns bone by given name. - * - * @param name - * the name of the bone - * @return found bone or null if none bone of a given name exists - */ - public BoneContext getBoneByName(String name) { - for(Entry entry : boneContexts.entrySet()) { - Bone bone = entry.getValue().getBone(); - if(bone != null && name.equals(bone.getName())) { - return entry.getValue(); - } - } - return null; - } - - /** - * This metod returns the default material. - * - * @return the default material - */ - public synchronized Material getDefaultMaterial() { - if (blenderKey.getDefaultMaterial() == null) { - Material defaultMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - defaultMaterial.setColor("Color", ColorRGBA.DarkGray); - blenderKey.setDefaultMaterial(defaultMaterial); - } - return blenderKey.getDefaultMaterial(); - } - - /** - * Clears all sotred resources and closes the blender input stream. - */ - public void dispose() { - LOGGER.fine("Disposing blender context resources."); - inputStream.forceClose(); - loadedFeatures.clear(); - loadedFeaturesByName.clear(); - parentStack.clear(); - loadedIpos.clear(); - modifiers.clear(); - constraints.clear(); - animData.clear(); - skeletons.clear(); - meshContexts.clear(); - boneContexts.clear(); - helpers.clear(); - } - - /** - * This enum defines what loaded data type user wants to retreive. It can be - * either filled structure or already converted data. - * - * @author Marcin Roguski - */ - public static enum LoadedFeatureDataType { - - LOADED_STRUCTURE(0), LOADED_FEATURE(1); - private int index; - - private LoadedFeatureDataType(int index) { - this.index = index; - } - - public int getIndex() { - return index; - } - } + private static final Logger LOGGER = Logger.getLogger(BlenderContext.class.getName()); + + /** The blender file version. */ + private int blenderVersion; + /** The blender key. */ + private BlenderKey blenderKey; + /** The header of the file block. */ + private DnaBlockData dnaBlockData; + /** The scene structure. */ + private Structure sceneStructure; + /** The input stream of the blend file. */ + private BlenderInputStream inputStream; + /** The asset manager. */ + private AssetManager assetManager; + /** + * A map containing the file block headers. The key is the old pointer + * address. + */ + private Map fileBlockHeadersByOma = new HashMap(); + /** A map containing the file block headers. The key is the block code. */ + private Map> fileBlockHeadersByCode = new HashMap>(); + /** + * This map stores the loaded features by their old memory address. The + * first object in the value table is the loaded structure and the second - + * the structure already converted into proper data. + */ + private Map loadedFeatures = new HashMap(); + /** + * This map stores the loaded features by their name. Only features with ID + * structure can be stored here. The first object in the value table is the + * loaded structure and the second - the structure already converted into + * proper data. + */ + private Map loadedFeaturesByName = new HashMap(); + /** A stack that hold the parent structure of currently loaded feature. */ + private Stack parentStack = new Stack(); + /** + * A map storing loaded ipos. The key is the ipo's owner old memory address + * and the value is the ipo. + */ + private Map loadedIpos = new HashMap(); + /** A list of modifiers for the specified object. */ + protected Map> modifiers = new HashMap>(); + /** A list of constraints for the specified object. */ + protected Map> constraints = new HashMap>(); + /** Anim data loaded for features. */ + private Map animData = new HashMap(); + /** Loaded skeletons. */ + private Map skeletons = new HashMap(); + /** A map of mesh contexts. */ + protected Map meshContexts = new HashMap(); + /** A map of bone contexts. */ + protected Map boneContexts = new HashMap(); + /** A map og helpers that perform loading. */ + private Map helpers = new HashMap(); + + /** + * This method sets the blender file version. + * + * @param blenderVersion + * the blender file version + */ + public void setBlenderVersion(String blenderVersion) { + this.blenderVersion = Integer.parseInt(blenderVersion); + } + + /** + * @return the blender file version + */ + public int getBlenderVersion() { + return blenderVersion; + } + + /** + * This method sets the blender key. + * + * @param blenderKey + * the blender key + */ + public void setBlenderKey(BlenderKey blenderKey) { + this.blenderKey = blenderKey; + } + + /** + * This method returns the blender key. + * + * @return the blender key + */ + public BlenderKey getBlenderKey() { + return blenderKey; + } + + /** + * This method sets the dna block data. + * + * @param dnaBlockData + * the dna block data + */ + public void setBlockData(DnaBlockData dnaBlockData) { + this.dnaBlockData = dnaBlockData; + } + + /** + * This method returns the dna block data. + * + * @return the dna block data + */ + public DnaBlockData getDnaBlockData() { + return dnaBlockData; + } + + /** + * This method sets the scene structure data. + * + * @param sceneStructure + * the scene structure data + */ + public void setSceneStructure(Structure sceneStructure) { + this.sceneStructure = sceneStructure; + } + + /** + * This method returns the scene structure data. + * + * @return the scene structure data + */ + public Structure getSceneStructure() { + return sceneStructure; + } + + /** + * This method returns the asset manager. + * + * @return the asset manager + */ + public AssetManager getAssetManager() { + return assetManager; + } + + /** + * This method sets the asset manager. + * + * @param assetManager + * the asset manager + */ + public void setAssetManager(AssetManager assetManager) { + this.assetManager = assetManager; + } + + /** + * This method returns the input stream of the blend file. + * + * @return the input stream of the blend file + */ + public BlenderInputStream getInputStream() { + return inputStream; + } + + /** + * This method sets the input stream of the blend file. + * + * @param inputStream + * the input stream of the blend file + */ + public void setInputStream(BlenderInputStream inputStream) { + this.inputStream = inputStream; + } + + /** + * This method adds a file block header to the map. Its old memory address + * is the key. + * + * @param oldMemoryAddress + * the address of the block header + * @param fileBlockHeader + * the block header to store + */ + public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) { + fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader); + List headers = fileBlockHeadersByCode.get(Integer.valueOf(fileBlockHeader.getCode())); + if (headers == null) { + headers = new ArrayList(); + fileBlockHeadersByCode.put(Integer.valueOf(fileBlockHeader.getCode()), headers); + } + headers.add(fileBlockHeader); + } + + /** + * This method returns the block header of a given memory address. If the + * header is not present then null is returned. + * + * @param oldMemoryAddress + * the address of the block header + * @return loaded header or null if it was not yet loaded + */ + public FileBlockHeader getFileBlock(Long oldMemoryAddress) { + return fileBlockHeadersByOma.get(oldMemoryAddress); + } + + /** + * This method returns a list of file blocks' headers of a specified code. + * + * @param code + * the code of file blocks + * @return a list of file blocks' headers of a specified code + */ + public List getFileBlocks(Integer code) { + return fileBlockHeadersByCode.get(code); + } + + /** + * This method clears the saved block headers stored in the features map. + */ + public void clearFileBlocks() { + fileBlockHeadersByOma.clear(); + fileBlockHeadersByCode.clear(); + } + + /** + * This method adds a helper instance to the helpers' map. + * + * @param + * the type of the helper + * @param clazz + * helper's class definition + * @param helper + * the helper instance + */ + public void putHelper(Class clazz, AbstractBlenderHelper helper) { + helpers.put(clazz.getSimpleName(), helper); + } + + @SuppressWarnings("unchecked") + public T getHelper(Class clazz) { + return (T) helpers.get(clazz.getSimpleName()); + } + + /** + * This method adds a loaded feature to the map. The key is its unique old + * memory address. + * + * @param oldMemoryAddress + * the address of the feature + * @param featureName + * the name of the feature + * @param structure + * the filled structure of the feature + * @param feature + * the feature we want to store + */ + public void addLoadedFeatures(Long oldMemoryAddress, String featureName, Structure structure, Object feature) { + if (oldMemoryAddress == null || structure == null || feature == null) { + throw new IllegalArgumentException("One of the given arguments is null!"); + } + Object[] storedData = new Object[] { structure, feature }; + loadedFeatures.put(oldMemoryAddress, storedData); + if (featureName != null) { + loadedFeaturesByName.put(featureName, storedData); + } + } + + /** + * This method returns the feature of a given memory address. If the feature + * is not yet loaded then null is returned. + * + * @param oldMemoryAddress + * the address of the feature + * @param loadedFeatureDataType + * the type of data we want to retreive it can be either filled + * structure or already converted feature + * @return loaded feature or null if it was not yet loaded + */ + public Object getLoadedFeature(Long oldMemoryAddress, LoadedFeatureDataType loadedFeatureDataType) { + Object[] result = loadedFeatures.get(oldMemoryAddress); + if (result != null) { + return result[loadedFeatureDataType.getIndex()]; + } + return null; + } + + /** + * This method returns the feature of a given name. If the feature is not + * yet loaded then null is returned. + * + * @param featureName + * the name of the feature + * @param loadedFeatureDataType + * the type of data we want to retreive it can be either filled + * structure or already converted feature + * @return loaded feature or null if it was not yet loaded + */ + public Object getLoadedFeature(String featureName, LoadedFeatureDataType loadedFeatureDataType) { + Object[] result = loadedFeaturesByName.get(featureName); + if (result != null) { + return result[loadedFeatureDataType.getIndex()]; + } + return null; + } + + /** + * This method clears the saved features stored in the features map. + */ + public void clearLoadedFeatures() { + loadedFeatures.clear(); + } + + /** + * This method adds the structure to the parent stack. + * + * @param parent + * the structure to be added to the stack + */ + public void pushParent(Structure parent) { + parentStack.push(parent); + } + + /** + * This method removes the structure from the top of the parent's stack. + * + * @return the structure that was removed from the stack + */ + public Structure popParent() { + try { + return parentStack.pop(); + } catch (EmptyStackException e) { + return null; + } + } + + /** + * This method retreives the structure at the top of the parent's stack but + * does not remove it. + * + * @return the structure from the top of the stack + */ + public Structure peekParent() { + try { + return parentStack.peek(); + } catch (EmptyStackException e) { + return null; + } + } + + /** + * This method adds new ipo curve for the feature. + * + * @param ownerOMA + * the OMA of blender feature that owns the ipo + * @param ipo + * the ipo to be added + */ + public void addIpo(Long ownerOMA, Ipo ipo) { + loadedIpos.put(ownerOMA, ipo); + } + + /** + * This method removes the ipo curve from the feature. + * + * @param ownerOma + * the OMA of blender feature that owns the ipo + */ + public Ipo removeIpo(Long ownerOma) { + return loadedIpos.remove(ownerOma); + } + + /** + * This method returns the ipo curve of the feature. + * + * @param ownerOMA + * the OMA of blender feature that owns the ipo + */ + public Ipo getIpo(Long ownerOMA) { + return loadedIpos.get(ownerOMA); + } + + /** + * This method adds a new modifier to the list. + * + * @param ownerOMA + * the owner's old memory address + * @param modifier + * the object's modifier + */ + public void addModifier(Long ownerOMA, Modifier modifier) { + List objectModifiers = this.modifiers.get(ownerOMA); + if (objectModifiers == null) { + objectModifiers = new ArrayList(); + this.modifiers.put(ownerOMA, objectModifiers); + } + objectModifiers.add(modifier); + } + + /** + * This method returns modifiers for the object specified by its old memory + * address and the modifier type. If no modifiers are found - empty list is + * returned. If the type is null - all modifiers for the object are + * returned. + * + * @param objectOMA + * object's old memory address + * @param type + * the type of the modifier + * @return the list of object's modifiers + */ + public List getModifiers(Long objectOMA, String type) { + List result = new ArrayList(); + List readModifiers = modifiers.get(objectOMA); + if (readModifiers != null && readModifiers.size() > 0) { + for (Modifier modifier : readModifiers) { + if (type == null || type.isEmpty() || modifier.getType().equals(type)) { + result.add(modifier); + } + } + } + return result; + } + + /** + * This method adds a new modifier to the list. + * + * @param ownerOMA + * the owner's old memory address + * @param constraints + * the object's constraints + */ + public void addConstraints(Long ownerOMA, List constraints) { + List objectConstraints = this.constraints.get(ownerOMA); + if (objectConstraints == null) { + objectConstraints = new ArrayList(); + this.constraints.put(ownerOMA, objectConstraints); + } + objectConstraints.addAll(constraints); + } + + /** + * This method returns constraints for the object specified by its old + * memory address. If no modifiers are found - null is returned. + * + * @param objectOMA + * object's old memory address + * @return the list of object's modifiers or null + */ + public List getConstraints(Long objectOMA) { + return objectOMA == null ? null : constraints.get(objectOMA); + } + + /** + * @return all available constraints + */ + public List getAllConstraints() { + List result = new ArrayList(); + for (Entry> entry : constraints.entrySet()) { + result.addAll(entry.getValue()); + } + return result; + } + + /** + * This method sets the anim data for the specified OMA of its owner. + * + * @param ownerOMA + * the owner's old memory address + * @param animData + * the animation data for the feature specified by ownerOMA + */ + public void setAnimData(Long ownerOMA, AnimData animData) { + this.animData.put(ownerOMA, animData); + } + + /** + * This method returns the animation data for the specified owner. + * + * @param ownerOMA + * the old memory address of the animation data owner + * @return the animation data or null if none exists + */ + public AnimData getAnimData(Long ownerOMA) { + return this.animData.get(ownerOMA); + } + + /** + * This method sets the skeleton for the specified OMA of its owner. + * + * @param skeletonOMA + * the skeleton's old memory address + * @param skeleton + * the skeleton specified by the given OMA + */ + public void setSkeleton(Long skeletonOMA, Skeleton skeleton) { + this.skeletons.put(skeletonOMA, skeleton); + } + + /** + * This method returns the skeleton for the specified OMA of its owner. + * + * @param skeletonOMA + * the skeleton's old memory address + * @return the skeleton specified by the given OMA + */ + public Skeleton getSkeleton(Long skeletonOMA) { + return this.skeletons.get(skeletonOMA); + } + + /** + * This method sets the mesh context for the given mesh old memory address. + * If the context is already set it will be replaced. + * + * @param meshOMA + * the mesh's old memory address + * @param meshContext + * the mesh's context + */ + public void setMeshContext(Long meshOMA, MeshContext meshContext) { + this.meshContexts.put(meshOMA, meshContext); + } + + /** + * This method returns the mesh context for the given mesh old memory + * address. If no context exists then null is returned. + * + * @param meshOMA + * the mesh's old memory address + * @return mesh's context + */ + public MeshContext getMeshContext(Long meshOMA) { + return this.meshContexts.get(meshOMA); + } + + /** + * This method sets the bone context for the given bone old memory address. + * If the context is already set it will be replaced. + * + * @param boneOMA + * the bone's old memory address + * @param boneContext + * the bones's context + */ + public void setBoneContext(Long boneOMA, BoneContext boneContext) { + this.boneContexts.put(boneOMA, boneContext); + } + + /** + * This method returns the bone context for the given bone old memory + * address. If no context exists then null is returned. + * + * @param boneOMA + * the bone's old memory address + * @return bone's context + */ + public BoneContext getBoneContext(Long boneOMA) { + return boneContexts.get(boneOMA); + } + + /** + * Returns bone by given name. + * + * @param name + * the name of the bone + * @return found bone or null if none bone of a given name exists + */ + public BoneContext getBoneByName(String name) { + for (Entry entry : boneContexts.entrySet()) { + Bone bone = entry.getValue().getBone(); + if (bone != null && name.equals(bone.getName())) { + return entry.getValue(); + } + } + return null; + } + + /** + * This metod returns the default material. + * + * @return the default material + */ + public synchronized Material getDefaultMaterial() { + if (blenderKey.getDefaultMaterial() == null) { + Material defaultMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + defaultMaterial.setColor("Color", ColorRGBA.DarkGray); + blenderKey.setDefaultMaterial(defaultMaterial); + } + return blenderKey.getDefaultMaterial(); + } + + /** + * Clears all sotred resources and closes the blender input stream. + */ + public void dispose() { + LOGGER.fine("Disposing blender context resources."); + inputStream.forceClose(); + loadedFeatures.clear(); + loadedFeaturesByName.clear(); + parentStack.clear(); + loadedIpos.clear(); + modifiers.clear(); + constraints.clear(); + animData.clear(); + skeletons.clear(); + meshContexts.clear(); + boneContexts.clear(); + helpers.clear(); + } + + /** + * This enum defines what loaded data type user wants to retreive. It can be + * either filled structure or already converted data. + * + * @author Marcin Roguski + */ + public static enum LoadedFeatureDataType { + + LOADED_STRUCTURE(0), LOADED_FEATURE(1); + private int index; + + private LoadedFeatureDataType(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java index 146ecfcf3..c7f5d19be 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderLoader.java @@ -70,158 +70,158 @@ import com.jme3.scene.plugins.blender.textures.TextureHelper; */ public class BlenderLoader extends AbstractBlenderLoader { - private static final Logger LOGGER = Logger.getLogger(BlenderLoader.class.getName()); - - /** The blocks read from the file. */ - protected List blocks; - - public Spatial load(AssetInfo assetInfo) throws IOException { - try { - this.setup(assetInfo); - - List sceneBlocks = new ArrayList(); - BlenderKey blenderKey = blenderContext.getBlenderKey(); - LoadingResults loadingResults = blenderKey.prepareLoadingResults(); - WorldData worldData = null;// a set of data used in different scene aspects - for (FileBlockHeader block : blocks) { - switch (block.getCode()) { - case FileBlockHeader.BLOCK_OB00:// Object - Object object = this.toObject(block.getStructure(blenderContext)); - if(object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { - loadingResults.addLight((LightNode) object); - } else if (object instanceof CameraNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0) { - loadingResults.addCamera((CameraNode) object); - } else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { - LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); - if (this.isRootObject(loadingResults, (Node)object)) { - loadingResults.addObject((Node) object); - } - } - break; -// case FileBlockHeader.BLOCK_MA00:// Material -// if (blenderKey.isLoadUnlinkedAssets() && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { -// loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext))); -// } -// break; - case FileBlockHeader.BLOCK_SC00:// Scene - if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.SCENES) != 0) { - sceneBlocks.add(block); - } - break; - case FileBlockHeader.BLOCK_WO00:// World - if (blenderKey.isLoadUnlinkedAssets() && worldData == null) {// onlu one world data is used - Structure worldStructure = block.getStructure(blenderContext); - String worldName = worldStructure.getName(); - if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) { - worldData = this.toWorldData(worldStructure); - if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { - loadingResults.addLight(worldData.getAmbientLight()); - } - } - } - break; - } - } - - //bake constraints after everything is loaded - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - constraintHelper.bakeConstraints(blenderContext); - - //load the scene at the very end so that the root nodes have no parent during loading or constraints applying - for(FileBlockHeader sceneBlock : sceneBlocks) { - loadingResults.addScene(this.toScene(sceneBlock.getStructure(blenderContext))); - } - - blenderContext.dispose(); - return loadingResults; - } catch (BlenderFileException e) { - LOGGER.log(Level.SEVERE, e.getMessage(), e); - } - return null; - } - - /** - * This method indicates if the given spatial is a root object. It means it - * has no parent or is directly attached to one of the already loaded scene - * nodes. - * - * @param loadingResults - * loading results containing the scene nodes - * @param spatial - * spatial object - * @return true if the given spatial is a root object and - * false otherwise - */ - protected boolean isRootObject(LoadingResults loadingResults, Spatial spatial) { - if(spatial.getParent() == null) { - return true; - } - for(Node scene : loadingResults.getScenes()) { - if(spatial.getParent().equals(scene)) { - return true; - } - } - return false; - } - - /** - * This method sets up the loader. - * @param assetInfo - * the asset info - * @throws BlenderFileException - * an exception is throw when something wrong happens with blender file - */ - protected void setup(AssetInfo assetInfo) throws BlenderFileException { - // registering loaders - ModelKey modelKey = (ModelKey) assetInfo.getKey(); - BlenderKey blenderKey; - if (modelKey instanceof BlenderKey) { - blenderKey = (BlenderKey) modelKey; - } else { - blenderKey = new BlenderKey(modelKey.getName()); - blenderKey.setAssetRootPath(modelKey.getFolder()); - } - - // opening stream - BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream()); - - // reading blocks - blocks = new ArrayList(); - FileBlockHeader fileBlock; - blenderContext = new BlenderContext(); - blenderContext.setBlenderVersion(inputStream.getVersionNumber()); - blenderContext.setAssetManager(assetInfo.getManager()); - blenderContext.setInputStream(inputStream); - blenderContext.setBlenderKey(blenderKey); - - // creating helpers - blenderContext.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); - blenderContext.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); - blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); - blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); - blenderContext.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); - blenderContext.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); - blenderContext.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); - blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); - blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); - blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext, blenderKey.isFixUpAxis())); - blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); - blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); - - // reading the blocks (dna block is automatically saved in the blender context when found) - FileBlockHeader sceneFileBlock = null; - do { - fileBlock = new FileBlockHeader(inputStream, blenderContext); - if (!fileBlock.isDnaBlock()) { - blocks.add(fileBlock); - // save the scene's file block - if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00) { - sceneFileBlock = fileBlock; - } - } - } while (!fileBlock.isLastBlock()); - if (sceneFileBlock != null) { - blenderContext.setSceneStructure(sceneFileBlock.getStructure(blenderContext)); + private static final Logger LOGGER = Logger.getLogger(BlenderLoader.class.getName()); + + /** The blocks read from the file. */ + protected List blocks; + + public Spatial load(AssetInfo assetInfo) throws IOException { + try { + this.setup(assetInfo); + + List sceneBlocks = new ArrayList(); + BlenderKey blenderKey = blenderContext.getBlenderKey(); + LoadingResults loadingResults = blenderKey.prepareLoadingResults(); + WorldData worldData = null;// a set of data used in different scene aspects + for (FileBlockHeader block : blocks) { + switch (block.getCode()) { + case FileBlockHeader.BLOCK_OB00:// Object + Object object = this.toObject(block.getStructure(blenderContext)); + if (object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { + loadingResults.addLight((LightNode) object); + } else if (object instanceof CameraNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0) { + loadingResults.addCamera((CameraNode) object); + } else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { + LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); + if (this.isRootObject(loadingResults, (Node) object)) { + loadingResults.addObject((Node) object); + } + } + break; + // case FileBlockHeader.BLOCK_MA00:// Material + // if (blenderKey.isLoadUnlinkedAssets() && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { + // loadingResults.addMaterial(this.toMaterial(block.getStructure(blenderContext))); + // } + // break; + case FileBlockHeader.BLOCK_SC00:// Scene + if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.SCENES) != 0) { + sceneBlocks.add(block); + } + break; + case FileBlockHeader.BLOCK_WO00:// World + if (blenderKey.isLoadUnlinkedAssets() && worldData == null) {// onlu one world data is used + Structure worldStructure = block.getStructure(blenderContext); + String worldName = worldStructure.getName(); + if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) { + worldData = this.toWorldData(worldStructure); + if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { + loadingResults.addLight(worldData.getAmbientLight()); + } + } + } + break; + } + } + + // bake constraints after everything is loaded + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + constraintHelper.bakeConstraints(blenderContext); + + // load the scene at the very end so that the root nodes have no parent during loading or constraints applying + for (FileBlockHeader sceneBlock : sceneBlocks) { + loadingResults.addScene(this.toScene(sceneBlock.getStructure(blenderContext))); + } + + blenderContext.dispose(); + return loadingResults; + } catch (BlenderFileException e) { + LOGGER.log(Level.SEVERE, e.getMessage(), e); + } + return null; + } + + /** + * This method indicates if the given spatial is a root object. It means it + * has no parent or is directly attached to one of the already loaded scene + * nodes. + * + * @param loadingResults + * loading results containing the scene nodes + * @param spatial + * spatial object + * @return true if the given spatial is a root object and + * false otherwise + */ + protected boolean isRootObject(LoadingResults loadingResults, Spatial spatial) { + if (spatial.getParent() == null) { + return true; + } + for (Node scene : loadingResults.getScenes()) { + if (spatial.getParent().equals(scene)) { + return true; + } + } + return false; + } + + /** + * This method sets up the loader. + * @param assetInfo + * the asset info + * @throws BlenderFileException + * an exception is throw when something wrong happens with blender file + */ + protected void setup(AssetInfo assetInfo) throws BlenderFileException { + // registering loaders + ModelKey modelKey = (ModelKey) assetInfo.getKey(); + BlenderKey blenderKey; + if (modelKey instanceof BlenderKey) { + blenderKey = (BlenderKey) modelKey; + } else { + blenderKey = new BlenderKey(modelKey.getName()); + blenderKey.setAssetRootPath(modelKey.getFolder()); + } + + // opening stream + BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream()); + + // reading blocks + blocks = new ArrayList(); + FileBlockHeader fileBlock; + blenderContext = new BlenderContext(); + blenderContext.setBlenderVersion(inputStream.getVersionNumber()); + blenderContext.setAssetManager(assetInfo.getManager()); + blenderContext.setInputStream(inputStream); + blenderContext.setBlenderKey(blenderKey); + + // creating helpers + blenderContext.putHelper(ArmatureHelper.class, new ArmatureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); + blenderContext.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); + blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); + blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); + blenderContext.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); + blenderContext.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); + blenderContext.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); + blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); + blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); + blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext, blenderKey.isFixUpAxis())); + blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); + blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderKey.isFixUpAxis())); + + // reading the blocks (dna block is automatically saved in the blender context when found) + FileBlockHeader sceneFileBlock = null; + do { + fileBlock = new FileBlockHeader(inputStream, blenderContext); + if (!fileBlock.isDnaBlock()) { + blocks.add(fileBlock); + // save the scene's file block + if (fileBlock.getCode() == FileBlockHeader.BLOCK_SC00) { + sceneFileBlock = fileBlock; + } + } + } while (!fileBlock.isLastBlock()); + if (sceneFileBlock != null) { + blenderContext.setSceneStructure(sceneFileBlock.getStructure(blenderContext)); } - } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java index e2f7a21b0..4deaf4e29 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderModelLoader.java @@ -57,30 +57,30 @@ public class BlenderModelLoader extends BlenderLoader { public Spatial load(AssetInfo assetInfo) throws IOException { try { this.setup(assetInfo); - + BlenderKey blenderKey = blenderContext.getBlenderKey(); Node modelRoot = new Node(blenderKey.getName()); - + for (FileBlockHeader block : blocks) { if (block.getCode() == FileBlockHeader.BLOCK_OB00) { Object object = this.toObject(block.getStructure(blenderContext)); - - if(object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { - modelRoot.addLight(((LightNode)object).getLight()); - modelRoot.attachChild((LightNode)object); - } else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { - LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); - if (((Node) object).getParent() == null) { - modelRoot.attachChild((Node)object); + + if (object instanceof LightNode && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) { + modelRoot.addLight(((LightNode) object).getLight()); + modelRoot.attachChild((LightNode) object); + } else if (object instanceof Node && (blenderKey.getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0) { + LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); + if (((Node) object).getParent() == null) { + modelRoot.attachChild((Node) object); } - } + } } } - - //bake constraints after everything is loaded - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - constraintHelper.bakeConstraints(blenderContext); - + + // bake constraints after everything is loaded + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + constraintHelper.bakeConstraints(blenderContext); + blenderContext.dispose(); return modelRoot; } catch (BlenderFileException e) { diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java index 79db7fa49..a0112aba6 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java @@ -56,215 +56,215 @@ import java.util.logging.Logger; * @author Marcin Roguski (Kaelthas) */ public class ArmatureHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(ArmatureHelper.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ArmatureHelper.class.getName()); - public static final String ARMETURE_NODE_MARKER = "armeture-node"; - - /** A map of bones and their old memory addresses. */ - private Map bonesOMAs = new HashMap(); + public static final String ARMETURE_NODE_MARKER = "armeture-node"; - /** - * This constructor parses the given blender version and stores the result. - * Some functionalities may differ in different blender versions. - * - * @param blenderVersion - * the version read from the blend file - * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not - */ - public ArmatureHelper(String blenderVersion, boolean fixUpAxis) { - super(blenderVersion, fixUpAxis); - } + /** A map of bones and their old memory addresses. */ + private Map bonesOMAs = new HashMap(); - /** - * This method builds the object's bones structure. - * - * @param boneStructure - * the structure containing the bones' data - * @param parent - * the parent bone - * @param result - * the list where the newly created bone will be added - * @param bonesPoseChannels - * a map of bones poses channels - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when there is problem with the blender - * file - */ - public void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List result, Matrix4f arbt, final Map bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException { - BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, arbt, bonesPoseChannels, blenderContext); - bc.buildBone(result, bonesOMAs, blenderContext); - } + /** + * This constructor parses the given blender version and stores the result. + * Some functionalities may differ in different blender versions. + * + * @param blenderVersion + * the version read from the blend file + * @param fixUpAxis + * a variable that indicates if the Y asxis is the UP axis or not + */ + public ArmatureHelper(String blenderVersion, boolean fixUpAxis) { + super(blenderVersion, fixUpAxis); + } - /** - * This method returns the old memory address of a bone. If the bone does - * not exist in the blend file - zero is returned. - * - * @param bone - * the bone whose old memory address we seek - * @return the old memory address of the given bone - */ - public Long getBoneOMA(Bone bone) { - Long result = bonesOMAs.get(bone); - if (result == null) { - result = Long.valueOf(0); - } - return result; - } + /** + * This method builds the object's bones structure. + * + * @param boneStructure + * the structure containing the bones' data + * @param parent + * the parent bone + * @param result + * the list where the newly created bone will be added + * @param bonesPoseChannels + * a map of bones poses channels + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when there is problem with the blender + * file + */ + public void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List result, Matrix4f arbt, final Map bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException { + BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, arbt, bonesPoseChannels, blenderContext); + bc.buildBone(result, bonesOMAs, blenderContext); + } - /** - * This method returns a map where the key is the object's group index that - * is used by a bone and the key is the bone index in the armature. - * - * @param defBaseStructure - * a bPose structure of the object - * @return bone group-to-index map - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public Map getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { - Map result = null; - if (skeleton.getBoneCount() != 0) { - result = new HashMap(); - List deformGroups = defBaseStructure.evaluateListBase(blenderContext);// bDeformGroup - int groupIndex = 0; - for (Structure deformGroup : deformGroups) { - String deformGroupName = deformGroup.getFieldValue("name").toString(); - int boneIndex = this.getBoneIndex(skeleton, deformGroupName); - if (boneIndex >= 0) { - result.put(groupIndex, boneIndex); - } - ++groupIndex; - } - } - return result; - } + /** + * This method returns the old memory address of a bone. If the bone does + * not exist in the blend file - zero is returned. + * + * @param bone + * the bone whose old memory address we seek + * @return the old memory address of the given bone + */ + public Long getBoneOMA(Bone bone) { + Long result = bonesOMAs.get(bone); + if (result == null) { + result = Long.valueOf(0); + } + return result; + } - @Override - public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - return true; - } + /** + * This method returns a map where the key is the object's group index that + * is used by a bone and the key is the bone index in the armature. + * + * @param defBaseStructure + * a bPose structure of the object + * @return bone group-to-index map + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Map getGroupToBoneIndexMap(Structure defBaseStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { + Map result = null; + if (skeleton.getBoneCount() != 0) { + result = new HashMap(); + List deformGroups = defBaseStructure.evaluateListBase(blenderContext);// bDeformGroup + int groupIndex = 0; + for (Structure deformGroup : deformGroups) { + String deformGroupName = deformGroup.getFieldValue("name").toString(); + int boneIndex = this.getBoneIndex(skeleton, deformGroupName); + if (boneIndex >= 0) { + result.put(groupIndex, boneIndex); + } + ++groupIndex; + } + } + return result; + } - /** - * This method retuns the bone tracks for animation. - * - * @param actionStructure - * the structure containing the tracks - * @param blenderContext - * the blender context - * @return a list of tracks for the specified animation - * @throws BlenderFileException - * an exception is thrown when there are problems with the blend - * file - */ - public BoneTrack[] getTracks(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { - if (blenderVersion < 250) { - return this.getTracks249(actionStructure, skeleton, blenderContext); - } else { - return this.getTracks250(actionStructure, skeleton, blenderContext); - } - } + @Override + public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { + return true; + } - /** - * This method retuns the bone tracks for animation for blender version 2.50 - * and higher. - * - * @param actionStructure - * the structure containing the tracks - * @param blenderContext - * the blender context - * @return a list of tracks for the specified animation - * @throws BlenderFileException - * an exception is thrown when there are problems with the blend - * file - */ - private BoneTrack[] getTracks250(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Getting tracks!"); - IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); - int fps = blenderContext.getBlenderKey().getFps(); - Structure groups = (Structure) actionStructure.getFieldValue("groups"); - List actionGroups = groups.evaluateListBase(blenderContext);// bActionGroup - List tracks = new ArrayList(); - for (Structure actionGroup : actionGroups) { - String name = actionGroup.getFieldValue("name").toString(); - int boneIndex = this.getBoneIndex(skeleton, name); - if (boneIndex >= 0) { - List channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(blenderContext); - BezierCurve[] bezierCurves = new BezierCurve[channels.size()]; - int channelCounter = 0; - for (Structure c : channels) { - int type = ipoHelper.getCurveType(c, blenderContext); - Pointer pBezTriple = (Pointer) c.getFieldValue("bezt"); - List bezTriples = pBezTriple.fetchData(blenderContext.getInputStream()); - bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2); - } + /** + * This method retuns the bone tracks for animation. + * + * @param actionStructure + * the structure containing the tracks + * @param blenderContext + * the blender context + * @return a list of tracks for the specified animation + * @throws BlenderFileException + * an exception is thrown when there are problems with the blend + * file + */ + public BoneTrack[] getTracks(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { + if (blenderVersion < 250) { + return this.getTracks249(actionStructure, skeleton, blenderContext); + } else { + return this.getTracks250(actionStructure, skeleton, blenderContext); + } + } - Bone bone = skeleton.getBone(boneIndex); - Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); - tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalRotation(), 0, ipo.getLastFrame(), fps, false)); - } - } - return tracks.toArray(new BoneTrack[tracks.size()]); - } + /** + * This method retuns the bone tracks for animation for blender version 2.50 + * and higher. + * + * @param actionStructure + * the structure containing the tracks + * @param blenderContext + * the blender context + * @return a list of tracks for the specified animation + * @throws BlenderFileException + * an exception is thrown when there are problems with the blend + * file + */ + private BoneTrack[] getTracks250(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Getting tracks!"); + IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); + int fps = blenderContext.getBlenderKey().getFps(); + Structure groups = (Structure) actionStructure.getFieldValue("groups"); + List actionGroups = groups.evaluateListBase(blenderContext);// bActionGroup + List tracks = new ArrayList(); + for (Structure actionGroup : actionGroups) { + String name = actionGroup.getFieldValue("name").toString(); + int boneIndex = this.getBoneIndex(skeleton, name); + if (boneIndex >= 0) { + List channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(blenderContext); + BezierCurve[] bezierCurves = new BezierCurve[channels.size()]; + int channelCounter = 0; + for (Structure c : channels) { + int type = ipoHelper.getCurveType(c, blenderContext); + Pointer pBezTriple = (Pointer) c.getFieldValue("bezt"); + List bezTriples = pBezTriple.fetchData(blenderContext.getInputStream()); + bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2); + } - /** - * This method retuns the bone tracks for animation for blender version 2.49 - * (and probably several lower versions too). - * - * @param actionStructure - * the structure containing the tracks - * @param blenderContext - * the blender context - * @return a list of tracks for the specified animation - * @throws BlenderFileException - * an exception is thrown when there are problems with the blend - * file - */ - private BoneTrack[] getTracks249(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Getting tracks!"); - IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); - int fps = blenderContext.getBlenderKey().getFps(); - Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase"); - List actionChannels = chanbase.evaluateListBase(blenderContext);// bActionChannel - List tracks = new ArrayList(); - for (Structure bActionChannel : actionChannels) { - String name = bActionChannel.getFieldValue("name").toString(); - int boneIndex = this.getBoneIndex(skeleton, name); - if (boneIndex >= 0) { - Pointer p = (Pointer) bActionChannel.getFieldValue("ipo"); - if (!p.isNull()) { - Structure ipoStructure = p.fetchData(blenderContext.getInputStream()).get(0); - - Bone bone = skeleton.getBone(boneIndex); - Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext); - if(ipo != null) { - tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalRotation(), 0, ipo.getLastFrame(), fps, false)); - } - } - } - } - return tracks.toArray(new BoneTrack[tracks.size()]); - } + Bone bone = skeleton.getBone(boneIndex); + Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); + tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalRotation(), 0, ipo.getLastFrame(), fps, false)); + } + } + return tracks.toArray(new BoneTrack[tracks.size()]); + } - /** - * This method returns the index of the bone in the given skeleton. - * - * @param skeleton - * the skeleton - * @param boneName - * the name of the bone - * @return the index of the bone - */ - private int getBoneIndex(Skeleton skeleton, String boneName) { - int result = -1; - for (int i = 0; i < skeleton.getBoneCount() && result == -1; ++i) { - if (boneName.equals(skeleton.getBone(i).getName())) { - result = i; - } - } - return result; - } + /** + * This method retuns the bone tracks for animation for blender version 2.49 + * (and probably several lower versions too). + * + * @param actionStructure + * the structure containing the tracks + * @param blenderContext + * the blender context + * @return a list of tracks for the specified animation + * @throws BlenderFileException + * an exception is thrown when there are problems with the blend + * file + */ + private BoneTrack[] getTracks249(Structure actionStructure, Skeleton skeleton, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Getting tracks!"); + IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); + int fps = blenderContext.getBlenderKey().getFps(); + Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase"); + List actionChannels = chanbase.evaluateListBase(blenderContext);// bActionChannel + List tracks = new ArrayList(); + for (Structure bActionChannel : actionChannels) { + String name = bActionChannel.getFieldValue("name").toString(); + int boneIndex = this.getBoneIndex(skeleton, name); + if (boneIndex >= 0) { + Pointer p = (Pointer) bActionChannel.getFieldValue("ipo"); + if (!p.isNull()) { + Structure ipoStructure = p.fetchData(blenderContext.getInputStream()).get(0); + + Bone bone = skeleton.getBone(boneIndex); + Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext); + if (ipo != null) { + tracks.add((BoneTrack) ipo.calculateTrack(boneIndex, bone.getLocalRotation(), 0, ipo.getLastFrame(), fps, false)); + } + } + } + } + return tracks.toArray(new BoneTrack[tracks.size()]); + } + + /** + * This method returns the index of the bone in the given skeleton. + * + * @param skeleton + * the skeleton + * @param boneName + * the name of the bone + * @return the index of the bone + */ + private int getBoneIndex(Skeleton skeleton, String boneName) { + int result = -1; + for (int i = 0; i < skeleton.getBoneCount() && result == -1; ++i) { + if (boneName.equals(skeleton.getBone(i).getName())) { + result = i; + } + } + return result; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java index 9635abfdd..b426d48e5 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java @@ -21,215 +21,215 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; * @author Marcin Roguski (Kaelthas) */ public class BoneContext { - /** The OMA of the bone's armature object. */ - private Long armatureObjectOMA; - /** The structure of the bone. */ - private Structure boneStructure; - /** Bone's pose channel structure. */ - private Structure poseChannel; - /** Bone's name. */ - private String boneName; - /** This variable indicates if the Y axis should be the UP axis. */ - private boolean fixUpAxis; - /** The bone's armature matrix. */ - private Matrix4f armatureMatrix; - /** The parent context. */ - private BoneContext parent; - /** The children of this context. */ - private List children = new ArrayList(); - /** Created bone (available after calling 'buildBone' method). */ - private Bone bone; - /** Bone's pose transform (available after calling 'buildBone' method). */ - private Transform poseTransform = new Transform(); - /** The bone's rest matrix. */ - private Matrix4f restMatrix; - /** Bone's total inverse transformation. */ - private Matrix4f inverseTotalTransformation; - /** Bone's parent inverse matrix. */ - private Matrix4f inverseParentMatrix; - /** The length of the bone. */ - private float length; - - /** - * Constructor. Creates the basic set of bone's data. - * - * @param armatureObjectOMA - * the OMA of the bone's armature object - * @param boneStructure - * the bone's structure - * @param objectToArmatureMatrix - * object-to-armature transformation matrix - * @param bonesPoseChannels - * a map of pose channels for each bone OMA - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when problem with blender data reading - * occurs - */ - public BoneContext(Long armatureObjectOMA, Structure boneStructure, Matrix4f objectToArmatureMatrix, final Map bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException { - this(boneStructure, armatureObjectOMA, null, objectToArmatureMatrix, bonesPoseChannels, blenderContext); - } - - /** - * Constructor. Creates the basic set of bone's data. - * - * @param boneStructure - * the bone's structure - * @param armatureObjectOMA - * the OMA of the bone's armature object - * @param parent - * bone's parent (null if the bone is the root bone) - * @param objectToArmatureMatrix - * object-to-armature transformation matrix - * @param bonesPoseChannels - * a map of pose channels for each bone OMA - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when problem with blender data reading - * occurs - */ - private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, Matrix4f objectToArmatureMatrix, final Map bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException { - this.parent = parent; - this.boneStructure = boneStructure; - this.armatureObjectOMA = armatureObjectOMA; - boneName = boneStructure.getFieldValue("name").toString(); - length = ((Number)boneStructure.getFieldValue("length")).floatValue(); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", true); - - fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis(); - this.computeRestMatrix(objectToArmatureMatrix); - List childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext); - for (Structure child : childbase) { - this.children.add(new BoneContext(child, armatureObjectOMA, this, objectToArmatureMatrix, bonesPoseChannels, blenderContext)); - } - - poseChannel = bonesPoseChannels.get(boneStructure.getOldMemoryAddress()); - - blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this); - } - - /** - * This method computes the rest matrix for the bone. - * - * @param objectToArmatureMatrix - * object-to-armature transformation matrix - */ - private void computeRestMatrix(Matrix4f objectToArmatureMatrix) { - if (parent != null) { - inverseParentMatrix = parent.inverseTotalTransformation.clone(); - } else if (fixUpAxis) { - inverseParentMatrix = objectToArmatureMatrix.clone(); - } else { - inverseParentMatrix = Matrix4f.IDENTITY.clone(); - } - - restMatrix = armatureMatrix.clone(); - inverseTotalTransformation = restMatrix.invert(); - - restMatrix = inverseParentMatrix.mult(restMatrix); - - for (BoneContext child : this.children) { - child.computeRestMatrix(objectToArmatureMatrix); - } - } - - /** - * This method computes the pose transform for the bone. - */ - @SuppressWarnings("unchecked") - private void computePoseTransform() { - DynamicArray loc = (DynamicArray) poseChannel.getFieldValue("loc"); - DynamicArray size = (DynamicArray) poseChannel.getFieldValue("size"); - DynamicArray quat = (DynamicArray) poseChannel.getFieldValue("quat"); - if (fixUpAxis) { - poseTransform.setTranslation(loc.get(0).floatValue(), -loc.get(2).floatValue(), loc.get(1).floatValue()); - poseTransform.setRotation(new Quaternion(quat.get(1).floatValue(), quat.get(3).floatValue(), -quat.get(2).floatValue(), quat.get(0).floatValue())); - poseTransform.setScale(size.get(0).floatValue(), size.get(2).floatValue(), size.get(1).floatValue()); - } else { - poseTransform.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue()); - poseTransform.setRotation(new Quaternion(quat.get(0).floatValue(), quat.get(1).floatValue(), quat.get(2).floatValue(), quat.get(3).floatValue())); - poseTransform.setScale(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue()); - } - - Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); - localTransform.setScale(bone.getLocalScale()); - localTransform.getTranslation().addLocal(poseTransform.getTranslation()); - localTransform.getRotation().multLocal(poseTransform.getRotation()); - localTransform.getScale().multLocal(poseTransform.getScale()); - - poseTransform.set(localTransform); - } - - /** - * This method builds the bone. It recursively builds the bone's children. - * - * @param bones - * a list of bones where the newly created bone will be added - * @param boneOMAs - * the map between bone and its old memory address - * @param blenderContext - * the blender context - * @return newly created bone - */ - public Bone buildBone(List bones, Map boneOMAs, BlenderContext blenderContext) { - Long boneOMA = boneStructure.getOldMemoryAddress(); - bone = new Bone(boneName); - bones.add(bone); - boneOMAs.put(bone, boneOMA); - blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone); - - Matrix4f pose = this.restMatrix.clone(); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - - Vector3f poseLocation = pose.toTranslationVector(); - Quaternion rotation = pose.toRotationQuat(); - Vector3f scale = objectHelper.getScale(pose); - - bone.setBindTransforms(poseLocation, rotation, scale); - for (BoneContext child : children) { - bone.addChild(child.buildBone(bones, boneOMAs, blenderContext)); - } - - this.computePoseTransform(); - - return bone; - } - - /** - * @return bone's pose transformation - */ - public Transform getPoseTransform() { - return poseTransform; - } - - /** - * @return built bone (available after calling 'buildBone' method) - */ - public Bone getBone() { - return bone; - } - - /** - * @return the old memory address of the bone - */ - public Long getBoneOma() { - return boneStructure.getOldMemoryAddress(); - } - - /** - * @return the length of the bone - */ - public float getLength() { - return length; - } - - /** - * @return OMA of the bone's armature object - */ - public Long getArmatureObjectOMA() { - return armatureObjectOMA; - } + /** The OMA of the bone's armature object. */ + private Long armatureObjectOMA; + /** The structure of the bone. */ + private Structure boneStructure; + /** Bone's pose channel structure. */ + private Structure poseChannel; + /** Bone's name. */ + private String boneName; + /** This variable indicates if the Y axis should be the UP axis. */ + private boolean fixUpAxis; + /** The bone's armature matrix. */ + private Matrix4f armatureMatrix; + /** The parent context. */ + private BoneContext parent; + /** The children of this context. */ + private List children = new ArrayList(); + /** Created bone (available after calling 'buildBone' method). */ + private Bone bone; + /** Bone's pose transform (available after calling 'buildBone' method). */ + private Transform poseTransform = new Transform(); + /** The bone's rest matrix. */ + private Matrix4f restMatrix; + /** Bone's total inverse transformation. */ + private Matrix4f inverseTotalTransformation; + /** Bone's parent inverse matrix. */ + private Matrix4f inverseParentMatrix; + /** The length of the bone. */ + private float length; + + /** + * Constructor. Creates the basic set of bone's data. + * + * @param armatureObjectOMA + * the OMA of the bone's armature object + * @param boneStructure + * the bone's structure + * @param objectToArmatureMatrix + * object-to-armature transformation matrix + * @param bonesPoseChannels + * a map of pose channels for each bone OMA + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when problem with blender data reading + * occurs + */ + public BoneContext(Long armatureObjectOMA, Structure boneStructure, Matrix4f objectToArmatureMatrix, final Map bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException { + this(boneStructure, armatureObjectOMA, null, objectToArmatureMatrix, bonesPoseChannels, blenderContext); + } + + /** + * Constructor. Creates the basic set of bone's data. + * + * @param boneStructure + * the bone's structure + * @param armatureObjectOMA + * the OMA of the bone's armature object + * @param parent + * bone's parent (null if the bone is the root bone) + * @param objectToArmatureMatrix + * object-to-armature transformation matrix + * @param bonesPoseChannels + * a map of pose channels for each bone OMA + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when problem with blender data reading + * occurs + */ + private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, Matrix4f objectToArmatureMatrix, final Map bonesPoseChannels, BlenderContext blenderContext) throws BlenderFileException { + this.parent = parent; + this.boneStructure = boneStructure; + this.armatureObjectOMA = armatureObjectOMA; + boneName = boneStructure.getFieldValue("name").toString(); + length = ((Number) boneStructure.getFieldValue("length")).floatValue(); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", true); + + fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis(); + this.computeRestMatrix(objectToArmatureMatrix); + List childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext); + for (Structure child : childbase) { + this.children.add(new BoneContext(child, armatureObjectOMA, this, objectToArmatureMatrix, bonesPoseChannels, blenderContext)); + } + + poseChannel = bonesPoseChannels.get(boneStructure.getOldMemoryAddress()); + + blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this); + } + + /** + * This method computes the rest matrix for the bone. + * + * @param objectToArmatureMatrix + * object-to-armature transformation matrix + */ + private void computeRestMatrix(Matrix4f objectToArmatureMatrix) { + if (parent != null) { + inverseParentMatrix = parent.inverseTotalTransformation.clone(); + } else if (fixUpAxis) { + inverseParentMatrix = objectToArmatureMatrix.clone(); + } else { + inverseParentMatrix = Matrix4f.IDENTITY.clone(); + } + + restMatrix = armatureMatrix.clone(); + inverseTotalTransformation = restMatrix.invert(); + + restMatrix = inverseParentMatrix.mult(restMatrix); + + for (BoneContext child : this.children) { + child.computeRestMatrix(objectToArmatureMatrix); + } + } + + /** + * This method computes the pose transform for the bone. + */ + @SuppressWarnings("unchecked") + private void computePoseTransform() { + DynamicArray loc = (DynamicArray) poseChannel.getFieldValue("loc"); + DynamicArray size = (DynamicArray) poseChannel.getFieldValue("size"); + DynamicArray quat = (DynamicArray) poseChannel.getFieldValue("quat"); + if (fixUpAxis) { + poseTransform.setTranslation(loc.get(0).floatValue(), -loc.get(2).floatValue(), loc.get(1).floatValue()); + poseTransform.setRotation(new Quaternion(quat.get(1).floatValue(), quat.get(3).floatValue(), -quat.get(2).floatValue(), quat.get(0).floatValue())); + poseTransform.setScale(size.get(0).floatValue(), size.get(2).floatValue(), size.get(1).floatValue()); + } else { + poseTransform.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue()); + poseTransform.setRotation(new Quaternion(quat.get(0).floatValue(), quat.get(1).floatValue(), quat.get(2).floatValue(), quat.get(3).floatValue())); + poseTransform.setScale(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue()); + } + + Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); + localTransform.setScale(bone.getLocalScale()); + localTransform.getTranslation().addLocal(poseTransform.getTranslation()); + localTransform.getRotation().multLocal(poseTransform.getRotation()); + localTransform.getScale().multLocal(poseTransform.getScale()); + + poseTransform.set(localTransform); + } + + /** + * This method builds the bone. It recursively builds the bone's children. + * + * @param bones + * a list of bones where the newly created bone will be added + * @param boneOMAs + * the map between bone and its old memory address + * @param blenderContext + * the blender context + * @return newly created bone + */ + public Bone buildBone(List bones, Map boneOMAs, BlenderContext blenderContext) { + Long boneOMA = boneStructure.getOldMemoryAddress(); + bone = new Bone(boneName); + bones.add(bone); + boneOMAs.put(bone, boneOMA); + blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone); + + Matrix4f pose = this.restMatrix.clone(); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + + Vector3f poseLocation = pose.toTranslationVector(); + Quaternion rotation = pose.toRotationQuat(); + Vector3f scale = objectHelper.getScale(pose); + + bone.setBindTransforms(poseLocation, rotation, scale); + for (BoneContext child : children) { + bone.addChild(child.buildBone(bones, boneOMAs, blenderContext)); + } + + this.computePoseTransform(); + + return bone; + } + + /** + * @return bone's pose transformation + */ + public Transform getPoseTransform() { + return poseTransform; + } + + /** + * @return built bone (available after calling 'buildBone' method) + */ + public Bone getBone() { + return bone; + } + + /** + * @return the old memory address of the bone + */ + public Long getBoneOma() { + return boneStructure.getOldMemoryAddress(); + } + + /** + * @return the length of the bone + */ + public float getLength() { + return length; + } + + /** + * @return OMA of the bone's armature object + */ + public Long getArmatureObjectOMA() { + return armatureObjectOMA; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/CalculationBone.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/CalculationBone.java index 90994ccf1..a8386203f 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/CalculationBone.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/CalculationBone.java @@ -13,116 +13,116 @@ import java.util.Arrays; * @author Marcin Roguski (Kaelthas) */ public class CalculationBone extends Node { - private Bone bone; - /** The bone's tracks. Will be altered at the end of calculation process. */ - private BoneTrack track; - /** The starting position of the bone. */ - private Vector3f startTranslation; - /** The starting rotation of the bone. */ - private Quaternion startRotation; - /** The starting scale of the bone. */ - private Vector3f startScale; - private Vector3f[] translations; - private Quaternion[] rotations; - private Vector3f[] scales; + private Bone bone; + /** The bone's tracks. Will be altered at the end of calculation process. */ + private BoneTrack track; + /** The starting position of the bone. */ + private Vector3f startTranslation; + /** The starting rotation of the bone. */ + private Quaternion startRotation; + /** The starting scale of the bone. */ + private Vector3f startScale; + private Vector3f[] translations; + private Quaternion[] rotations; + private Vector3f[] scales; - public CalculationBone(Bone bone, int boneFramesCount) { - this.bone = bone; - this.startRotation = bone.getModelSpaceRotation().clone(); - this.startTranslation = bone.getModelSpacePosition().clone(); - this.startScale = bone.getModelSpaceScale().clone(); - this.reset(); - if(boneFramesCount > 0) { - this.translations = new Vector3f[boneFramesCount]; - this.rotations = new Quaternion[boneFramesCount]; - this.scales = new Vector3f[boneFramesCount]; - - Arrays.fill(this.translations, 0, boneFramesCount, this.startTranslation); - Arrays.fill(this.rotations, 0, boneFramesCount, this.startRotation); - Arrays.fill(this.scales, 0, boneFramesCount, this.startScale); - } - } - - /** - * Constructor. Stores the track, starting transformation and sets the transformation to the starting positions. - * @param bone - * the bone this class will imitate - * @param track - * the bone's tracks - */ - public CalculationBone(Bone bone, BoneTrack track) { - this(bone, 0); - this.track = track; - this.translations = track.getTranslations(); - this.rotations = track.getRotations(); - this.scales = track.getScales(); - } + public CalculationBone(Bone bone, int boneFramesCount) { + this.bone = bone; + this.startRotation = bone.getModelSpaceRotation().clone(); + this.startTranslation = bone.getModelSpacePosition().clone(); + this.startScale = bone.getModelSpaceScale().clone(); + this.reset(); + if (boneFramesCount > 0) { + this.translations = new Vector3f[boneFramesCount]; + this.rotations = new Quaternion[boneFramesCount]; + this.scales = new Vector3f[boneFramesCount]; - public int getBoneFramesCount() { - return this.translations==null ? 0 : this.translations.length; - } - - /** - * This method returns the end point of the bone. If the bone has parent it is calculated from the start point - * of parent to the start point of this bone. If the bone doesn't have a parent the end location is considered - * to be 1 point up along Y axis (scale is applied if set to != 1.0); - * @return the end point of this bone - */ - //TODO: set to Z axis if user defined it this way - public Vector3f getEndPoint() { - if (this.getParent() == null) { - return new Vector3f(0, this.getLocalScale().y, 0); - } else { - Node parent = this.getParent(); - return parent.getWorldTranslation().subtract(this.getWorldTranslation()).multLocal(this.getWorldScale()); - } - } + Arrays.fill(this.translations, 0, boneFramesCount, this.startTranslation); + Arrays.fill(this.rotations, 0, boneFramesCount, this.startRotation); + Arrays.fill(this.scales, 0, boneFramesCount, this.startScale); + } + } - /** - * This method resets the calculation bone to the starting position. - */ - public void reset() { - this.setLocalTranslation(startTranslation); - this.setLocalRotation(startRotation); - this.setLocalScale(startScale); - } + /** + * Constructor. Stores the track, starting transformation and sets the transformation to the starting positions. + * @param bone + * the bone this class will imitate + * @param track + * the bone's tracks + */ + public CalculationBone(Bone bone, BoneTrack track) { + this(bone, 0); + this.track = track; + this.translations = track.getTranslations(); + this.rotations = track.getRotations(); + this.scales = track.getScales(); + } - @Override - public int attachChild(Spatial child) { - if (this.getChildren() != null && this.getChildren().size() > 1) { - throw new IllegalStateException(this.getClass().getName() + " class instance can only have one child!"); - } - return super.attachChild(child); - } + public int getBoneFramesCount() { + return this.translations == null ? 0 : this.translations.length; + } - public Spatial rotate(Quaternion rot, int frame) { - Spatial spatial = super.rotate(rot); - this.updateWorldTransforms(); - if (this.getChildren() != null && this.getChildren().size() > 0) { - CalculationBone child = (CalculationBone) this.getChild(0); - child.updateWorldTransforms(); - } - rotations[frame].set(this.getLocalRotation()); - translations[frame].set(this.getLocalTranslation()); - if (scales != null) { - scales[frame].set(this.getLocalScale()); - } - return spatial; - } + /** + * This method returns the end point of the bone. If the bone has parent it is calculated from the start point + * of parent to the start point of this bone. If the bone doesn't have a parent the end location is considered + * to be 1 point up along Y axis (scale is applied if set to != 1.0); + * @return the end point of this bone + */ + // TODO: set to Z axis if user defined it this way + public Vector3f getEndPoint() { + if (this.getParent() == null) { + return new Vector3f(0, this.getLocalScale().y, 0); + } else { + Node parent = this.getParent(); + return parent.getWorldTranslation().subtract(this.getWorldTranslation()).multLocal(this.getWorldScale()); + } + } - public void applyCalculatedTracks() { - if(track != null) { - track.setKeyframes(track.getTimes(), translations, rotations, scales); - } else { - bone.setUserControl(true); - bone.setUserTransforms(translations[0], rotations[0], scales[0]); - bone.setUserControl(false); - bone.updateWorldVectors(); - } - } + /** + * This method resets the calculation bone to the starting position. + */ + public void reset() { + this.setLocalTranslation(startTranslation); + this.setLocalRotation(startRotation); + this.setLocalScale(startScale); + } - @Override - public String toString() { - return bone.getName() + ": " + this.getLocalRotation() + " " + this.getLocalTranslation(); - } + @Override + public int attachChild(Spatial child) { + if (this.getChildren() != null && this.getChildren().size() > 1) { + throw new IllegalStateException(this.getClass().getName() + " class instance can only have one child!"); + } + return super.attachChild(child); + } + + public Spatial rotate(Quaternion rot, int frame) { + Spatial spatial = super.rotate(rot); + this.updateWorldTransforms(); + if (this.getChildren() != null && this.getChildren().size() > 0) { + CalculationBone child = (CalculationBone) this.getChild(0); + child.updateWorldTransforms(); + } + rotations[frame].set(this.getLocalRotation()); + translations[frame].set(this.getLocalTranslation()); + if (scales != null) { + scales[frame].set(this.getLocalScale()); + } + return spatial; + } + + public void applyCalculatedTracks() { + if (track != null) { + track.setKeyframes(track.getTimes(), translations, rotations, scales); + } else { + bone.setUserControl(true); + bone.setUserTransforms(translations[0], rotations[0], scales[0]); + bone.setUserControl(false); + bone.updateWorldVectors(); + } + } + + @Override + public String toString() { + return bone.getName() + ": " + this.getLocalRotation() + " " + this.getLocalTranslation(); + } } \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java index a969922b6..790269262 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java @@ -18,234 +18,234 @@ import com.jme3.scene.plugins.blender.curves.BezierCurve; * @author Marcin Roguski */ public class Ipo { - private static final Logger LOGGER = Logger.getLogger(Ipo.class.getName()); - - public static final int AC_LOC_X = 1; - public static final int AC_LOC_Y = 2; - public static final int AC_LOC_Z = 3; - public static final int OB_ROT_X = 7; - public static final int OB_ROT_Y = 8; - public static final int OB_ROT_Z = 9; - public static final int AC_SIZE_X = 13; - public static final int AC_SIZE_Y = 14; - public static final int AC_SIZE_Z = 15; - public static final int AC_QUAT_W = 25; - public static final int AC_QUAT_X = 26; - public static final int AC_QUAT_Y = 27; - public static final int AC_QUAT_Z = 28; - - /** A list of bezier curves for this interpolation object. */ - private BezierCurve[] bezierCurves; - /** Each ipo contains one bone track. */ - private Track calculatedTrack; - /** This variable indicates if the Y asxis is the UP axis or not. */ - protected boolean fixUpAxis; - /** Depending on the blender version rotations are stored in degrees or radians so we need to know the version that is used. */ - protected final int blenderVersion; - - /** - * Constructor. Stores the bezier curves. - * - * @param bezierCurves - * a table of bezier curves - * @param fixUpAxis - * indicates if the Y is the up axis or not - * @param blenderVersion - * the blender version that is currently used - */ - public Ipo(BezierCurve[] bezierCurves, boolean fixUpAxis, int blenderVersion) { - this.bezierCurves = bezierCurves; - this.fixUpAxis = fixUpAxis; - this.blenderVersion = blenderVersion; - } - - /** - * This method calculates the ipo value for the first curve. - * - * @param frame - * the frame for which the value is calculated - * @return calculated ipo value - */ - public float calculateValue(int frame) { - return this.calculateValue(frame, 0); - } - - /** - * This method calculates the ipo value for the curve of the specified - * index. Make sure you do not exceed the curves amount. Alway chech the - * amount of curves before calling this method. - * - * @param frame - * the frame for which the value is calculated - * @param curveIndex - * the index of the curve - * @return calculated ipo value - */ - public float calculateValue(int frame, int curveIndex) { - return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE); - } - - /** - * This method returns the curves amount. - * - * @return the curves amount - */ - public int getCurvesAmount() { - return bezierCurves.length; - } - - /** - * This method returns the frame where last bezier triple center point of - * the specified bezier curve is located. - * - * @return the frame number of the last defined bezier triple point for the - * specified ipo - */ - public int getLastFrame() { - int result = 1; - for (int i = 0; i < bezierCurves.length; ++i) { - int tempResult = bezierCurves[i].getLastFrame(); - if (tempResult > result) { - result = tempResult; - } - } - return result; - } - - /** - * This method calculates the value of the curves as a bone track between - * the specified frames. - * - * @param targetIndex - * the index of the target for which the method calculates the - * tracks IMPORTANT! Aet to -1 (or any negative number) if you - * want to load spatial animation. - * @param localQuaternionRotation - * the local rotation of the object/bone that will be animated by - * the track - * @param startFrame - * the firs frame of tracks (inclusive) - * @param stopFrame - * the last frame of the tracks (inclusive) - * @param fps - * frame rate (frames per second) - * @param spatialTrack - * this flag indicates if the track belongs to a spatial or to a - * bone; the diference is important because it appears that bones - * in blender have the same type of coordinate system (Y as UP) - * as jme while other features have different one (Z is UP) - * @return bone track for the specified bone - */ - public Track calculateTrack(int targetIndex, Quaternion localQuaternionRotation, int startFrame, int stopFrame, int fps, boolean spatialTrack) { - if (calculatedTrack == null) { - // preparing data for track - int framesAmount = stopFrame - startFrame; - float timeBetweenFrames = 1.0f / fps; - - float[] times = new float[framesAmount + 1]; - Vector3f[] translations = new Vector3f[framesAmount + 1]; - float[] translation = new float[3]; - Quaternion[] rotations = new Quaternion[framesAmount + 1]; - float[] quaternionRotation = new float[] { 0, 0, 0, 1 }; - float[] objectRotation = new float[3]; - Vector3f[] scales = new Vector3f[framesAmount + 1]; - float[] scale = new float[] { 1.0f, 1.0f, 1.0f }; - float degreeToRadiansFactor = 1; - if(blenderVersion < 250) {//in blender earlier than 2.50 the values are stored in degrees - degreeToRadiansFactor *= FastMath.DEG_TO_RAD * 10;// the values in blender are divided by 10, so we need to mult it here - } - - // calculating track data - for (int frame = startFrame; frame <= stopFrame; ++frame) { - int index = frame - startFrame; - times[index] = index * timeBetweenFrames;//start + (frame - 1) * timeBetweenFrames; - for (int j = 0; j < bezierCurves.length; ++j) { - double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE); - switch (bezierCurves[j].getType()) { - // LOCATION - case AC_LOC_X: - translation[0] = (float) value; - break; - case AC_LOC_Y: - if (fixUpAxis) { - translation[2] = (float) -value; - } else { - translation[1] = (float) value; - } - break; - case AC_LOC_Z: - translation[fixUpAxis ? 1 : 2] = (float) value; - break; - - // ROTATION (used with object animation) - // the value here is in degrees divided by 10 (so in - // example: 9 = PI/2) - case OB_ROT_X: - objectRotation[0] = (float) value * degreeToRadiansFactor; - break; - case OB_ROT_Y: - if (fixUpAxis) { - objectRotation[2] = (float) -value * degreeToRadiansFactor; - } else { - objectRotation[1] = (float) value * degreeToRadiansFactor; - } - break; - case OB_ROT_Z: - objectRotation[fixUpAxis ? 1 : 2] = (float) value * degreeToRadiansFactor; - break; - - // SIZE - case AC_SIZE_X: - scale[0] = (float) value; - break; - case AC_SIZE_Y: - if (fixUpAxis) { - scale[2] = (float) value; - } else { - scale[1] = (float) value; - } - break; - case AC_SIZE_Z: - scale[fixUpAxis ? 1 : 2] = (float) value; - break; - - // QUATERNION ROTATION (used with bone animation), dunno - // why but here we shouldn't check the - // spatialTrack flag value - case AC_QUAT_W: - quaternionRotation[3] = (float) value; - break; - case AC_QUAT_X: - quaternionRotation[0] = (float) value; - break; - case AC_QUAT_Y: - if (fixUpAxis) { - quaternionRotation[2] = -(float) value; - } else { - quaternionRotation[1] = (float) value; - } - break; - case AC_QUAT_Z: - if (fixUpAxis) { - quaternionRotation[1] = (float) value; - } else { - quaternionRotation[2] = (float) value; - } - break; - default: - LOGGER.warning("Unknown ipo curve type: " + bezierCurves[j].getType()); - } - } - translations[index] = localQuaternionRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2])); - rotations[index] = spatialTrack ? new Quaternion().fromAngles(objectRotation) : new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]); - scales[index] = new Vector3f(scale[0], scale[1], scale[2]); - } - if (spatialTrack) { - calculatedTrack = new SpatialTrack(times, translations, rotations, scales); - } else { - calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales); - } - } - return calculatedTrack; - } + private static final Logger LOGGER = Logger.getLogger(Ipo.class.getName()); + + public static final int AC_LOC_X = 1; + public static final int AC_LOC_Y = 2; + public static final int AC_LOC_Z = 3; + public static final int OB_ROT_X = 7; + public static final int OB_ROT_Y = 8; + public static final int OB_ROT_Z = 9; + public static final int AC_SIZE_X = 13; + public static final int AC_SIZE_Y = 14; + public static final int AC_SIZE_Z = 15; + public static final int AC_QUAT_W = 25; + public static final int AC_QUAT_X = 26; + public static final int AC_QUAT_Y = 27; + public static final int AC_QUAT_Z = 28; + + /** A list of bezier curves for this interpolation object. */ + private BezierCurve[] bezierCurves; + /** Each ipo contains one bone track. */ + private Track calculatedTrack; + /** This variable indicates if the Y asxis is the UP axis or not. */ + protected boolean fixUpAxis; + /** Depending on the blender version rotations are stored in degrees or radians so we need to know the version that is used. */ + protected final int blenderVersion; + + /** + * Constructor. Stores the bezier curves. + * + * @param bezierCurves + * a table of bezier curves + * @param fixUpAxis + * indicates if the Y is the up axis or not + * @param blenderVersion + * the blender version that is currently used + */ + public Ipo(BezierCurve[] bezierCurves, boolean fixUpAxis, int blenderVersion) { + this.bezierCurves = bezierCurves; + this.fixUpAxis = fixUpAxis; + this.blenderVersion = blenderVersion; + } + + /** + * This method calculates the ipo value for the first curve. + * + * @param frame + * the frame for which the value is calculated + * @return calculated ipo value + */ + public float calculateValue(int frame) { + return this.calculateValue(frame, 0); + } + + /** + * This method calculates the ipo value for the curve of the specified + * index. Make sure you do not exceed the curves amount. Alway chech the + * amount of curves before calling this method. + * + * @param frame + * the frame for which the value is calculated + * @param curveIndex + * the index of the curve + * @return calculated ipo value + */ + public float calculateValue(int frame, int curveIndex) { + return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE); + } + + /** + * This method returns the curves amount. + * + * @return the curves amount + */ + public int getCurvesAmount() { + return bezierCurves.length; + } + + /** + * This method returns the frame where last bezier triple center point of + * the specified bezier curve is located. + * + * @return the frame number of the last defined bezier triple point for the + * specified ipo + */ + public int getLastFrame() { + int result = 1; + for (int i = 0; i < bezierCurves.length; ++i) { + int tempResult = bezierCurves[i].getLastFrame(); + if (tempResult > result) { + result = tempResult; + } + } + return result; + } + + /** + * This method calculates the value of the curves as a bone track between + * the specified frames. + * + * @param targetIndex + * the index of the target for which the method calculates the + * tracks IMPORTANT! Aet to -1 (or any negative number) if you + * want to load spatial animation. + * @param localQuaternionRotation + * the local rotation of the object/bone that will be animated by + * the track + * @param startFrame + * the firs frame of tracks (inclusive) + * @param stopFrame + * the last frame of the tracks (inclusive) + * @param fps + * frame rate (frames per second) + * @param spatialTrack + * this flag indicates if the track belongs to a spatial or to a + * bone; the diference is important because it appears that bones + * in blender have the same type of coordinate system (Y as UP) + * as jme while other features have different one (Z is UP) + * @return bone track for the specified bone + */ + public Track calculateTrack(int targetIndex, Quaternion localQuaternionRotation, int startFrame, int stopFrame, int fps, boolean spatialTrack) { + if (calculatedTrack == null) { + // preparing data for track + int framesAmount = stopFrame - startFrame; + float timeBetweenFrames = 1.0f / fps; + + float[] times = new float[framesAmount + 1]; + Vector3f[] translations = new Vector3f[framesAmount + 1]; + float[] translation = new float[3]; + Quaternion[] rotations = new Quaternion[framesAmount + 1]; + float[] quaternionRotation = new float[] { 0, 0, 0, 1 }; + float[] objectRotation = new float[3]; + Vector3f[] scales = new Vector3f[framesAmount + 1]; + float[] scale = new float[] { 1.0f, 1.0f, 1.0f }; + float degreeToRadiansFactor = 1; + if (blenderVersion < 250) {// in blender earlier than 2.50 the values are stored in degrees + degreeToRadiansFactor *= FastMath.DEG_TO_RAD * 10;// the values in blender are divided by 10, so we need to mult it here + } + + // calculating track data + for (int frame = startFrame; frame <= stopFrame; ++frame) { + int index = frame - startFrame; + times[index] = index * timeBetweenFrames;// start + (frame - 1) * timeBetweenFrames; + for (int j = 0; j < bezierCurves.length; ++j) { + double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE); + switch (bezierCurves[j].getType()) { + // LOCATION + case AC_LOC_X: + translation[0] = (float) value; + break; + case AC_LOC_Y: + if (fixUpAxis) { + translation[2] = (float) -value; + } else { + translation[1] = (float) value; + } + break; + case AC_LOC_Z: + translation[fixUpAxis ? 1 : 2] = (float) value; + break; + + // ROTATION (used with object animation) + // the value here is in degrees divided by 10 (so in + // example: 9 = PI/2) + case OB_ROT_X: + objectRotation[0] = (float) value * degreeToRadiansFactor; + break; + case OB_ROT_Y: + if (fixUpAxis) { + objectRotation[2] = (float) -value * degreeToRadiansFactor; + } else { + objectRotation[1] = (float) value * degreeToRadiansFactor; + } + break; + case OB_ROT_Z: + objectRotation[fixUpAxis ? 1 : 2] = (float) value * degreeToRadiansFactor; + break; + + // SIZE + case AC_SIZE_X: + scale[0] = (float) value; + break; + case AC_SIZE_Y: + if (fixUpAxis) { + scale[2] = (float) value; + } else { + scale[1] = (float) value; + } + break; + case AC_SIZE_Z: + scale[fixUpAxis ? 1 : 2] = (float) value; + break; + + // QUATERNION ROTATION (used with bone animation), dunno + // why but here we shouldn't check the + // spatialTrack flag value + case AC_QUAT_W: + quaternionRotation[3] = (float) value; + break; + case AC_QUAT_X: + quaternionRotation[0] = (float) value; + break; + case AC_QUAT_Y: + if (fixUpAxis) { + quaternionRotation[2] = -(float) value; + } else { + quaternionRotation[1] = (float) value; + } + break; + case AC_QUAT_Z: + if (fixUpAxis) { + quaternionRotation[1] = (float) value; + } else { + quaternionRotation[2] = (float) value; + } + break; + default: + LOGGER.warning("Unknown ipo curve type: " + bezierCurves[j].getType()); + } + } + translations[index] = localQuaternionRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2])); + rotations[index] = spatialTrack ? new Quaternion().fromAngles(objectRotation) : new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]); + scales[index] = new Vector3f(scale[0], scale[1], scale[2]); + } + if (spatialTrack) { + calculatedTrack = new SpatialTrack(times, translations, rotations, scales); + } else { + calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales); + } + } + return calculatedTrack; + } } \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java index d6e6cd50b..6e0d9961e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/IpoHelper.java @@ -21,182 +21,182 @@ import java.util.logging.Logger; * @author Marcin Roguski */ public class IpoHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(IpoHelper.class.getName()); - - /** - * This constructor parses the given blender version and stores the result. - * Some functionalities may differ in different blender versions. - * - * @param blenderVersion - * the version read from the blend file - * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not - */ - public IpoHelper(String blenderVersion, boolean fixUpAxis) { - super(blenderVersion, fixUpAxis); - } - - /** - * This method creates an ipo object used for interpolation calculations. - * - * @param ipoStructure - * the structure with ipo definition - * @param blenderContext - * the blender context - * @return the ipo object - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException { - Structure curvebase = (Structure) ipoStructure.getFieldValue("curve"); - - // preparing bezier curves - Ipo result = null; - List curves = curvebase.evaluateListBase(blenderContext);// IpoCurve - if (curves.size() > 0) { - BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; - int frame = 0; - for (Structure curve : curves) { - Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); - List bezTriples = pBezTriple.fetchData(blenderContext.getInputStream()); - int type = ((Number) curve.getFieldValue("adrcode")).intValue(); - bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); - } - curves.clear(); - result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); - blenderContext.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result); - } - return result; - } - - /** - * This method creates an ipo object used for interpolation calculations. It - * should be called for blender version 2.50 and higher. - * - * @param actionStructure - * the structure with action definition - * @param blenderContext - * the blender context - * @return the ipo object - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public Ipo fromAction(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { - Ipo result = null; - List curves = ((Structure) actionStructure.getFieldValue("curves")).evaluateListBase(blenderContext);// FCurve - if (curves.size() > 0) { - BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; - int frame = 0; - for (Structure curve : curves) { - Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); - List bezTriples = pBezTriple.fetchData(blenderContext.getInputStream()); - int type = this.getCurveType(curve, blenderContext); - bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); - } - curves.clear(); - result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); - } - return result; - } - - /** - * This method returns the type of the ipo curve. - * - * @param structure - * the structure must contain the 'rna_path' field and - * 'array_index' field (the type is not important here) - * @param blenderContext - * the blender context - * @return the type of the curve - */ - public int getCurveType(Structure structure, BlenderContext blenderContext) { - // reading rna path first - BlenderInputStream bis = blenderContext.getInputStream(); - int currentPosition = bis.getPosition(); - Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path"); - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress()); - bis.setPosition(dataFileBlock.getBlockPosition()); - String rnaPath = bis.readString(); - bis.setPosition(currentPosition); - int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue(); - - // determining the curve type - if (rnaPath.endsWith("location")) { - return Ipo.AC_LOC_X + arrayIndex; - } - if (rnaPath.endsWith("rotation_quaternion")) { - return Ipo.AC_QUAT_W + arrayIndex; - } - if (rnaPath.endsWith("scale")) { - return Ipo.AC_SIZE_X + arrayIndex; - } - if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) { - return Ipo.OB_ROT_X + arrayIndex; - } - LOGGER.warning("Unknown curve rna path: " + rnaPath); - return -1; - } - - /** - * This method creates an ipo with only a single value. No track type is - * specified so do not use it for calculating tracks. - * - * @param constValue - * the value of this ipo - * @return constant ipo - */ - public Ipo fromValue(float constValue) { - return new ConstIpo(constValue); - } - - @Override - public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - return true; - } - - /** - * Ipo constant curve. This is a curve with only one value and no specified - * type. This type of ipo cannot be used to calculate tracks. It should only - * be used to calculate single value for a given frame. - * - * @author Marcin Roguski - */ - private class ConstIpo extends Ipo { - - /** The constant value of this ipo. */ - private float constValue; - - /** - * Constructor. Stores the constant value of this ipo. - * - * @param constValue - * the constant value of this ipo - */ - public ConstIpo(float constValue) { - super(null, false, 0);//the version is not important here - this.constValue = constValue; - } - - @Override - public float calculateValue(int frame) { - return constValue; - } - - @Override - public float calculateValue(int frame, int curveIndex) { - return constValue; - } - - @Override - public int getCurvesAmount() { - return 0; - } - - @Override - public BoneTrack calculateTrack(int boneIndex, Quaternion localQuaternionRotation, int startFrame, int stopFrame, int fps, boolean boneTrack) { - throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!"); - } - } + private static final Logger LOGGER = Logger.getLogger(IpoHelper.class.getName()); + + /** + * This constructor parses the given blender version and stores the result. + * Some functionalities may differ in different blender versions. + * + * @param blenderVersion + * the version read from the blend file + * @param fixUpAxis + * a variable that indicates if the Y asxis is the UP axis or not + */ + public IpoHelper(String blenderVersion, boolean fixUpAxis) { + super(blenderVersion, fixUpAxis); + } + + /** + * This method creates an ipo object used for interpolation calculations. + * + * @param ipoStructure + * the structure with ipo definition + * @param blenderContext + * the blender context + * @return the ipo object + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException { + Structure curvebase = (Structure) ipoStructure.getFieldValue("curve"); + + // preparing bezier curves + Ipo result = null; + List curves = curvebase.evaluateListBase(blenderContext);// IpoCurve + if (curves.size() > 0) { + BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; + int frame = 0; + for (Structure curve : curves) { + Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); + List bezTriples = pBezTriple.fetchData(blenderContext.getInputStream()); + int type = ((Number) curve.getFieldValue("adrcode")).intValue(); + bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); + } + curves.clear(); + result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); + blenderContext.addLoadedFeatures(ipoStructure.getOldMemoryAddress(), ipoStructure.getName(), ipoStructure, result); + } + return result; + } + + /** + * This method creates an ipo object used for interpolation calculations. It + * should be called for blender version 2.50 and higher. + * + * @param actionStructure + * the structure with action definition + * @param blenderContext + * the blender context + * @return the ipo object + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Ipo fromAction(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { + Ipo result = null; + List curves = ((Structure) actionStructure.getFieldValue("curves")).evaluateListBase(blenderContext);// FCurve + if (curves.size() > 0) { + BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; + int frame = 0; + for (Structure curve : curves) { + Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); + List bezTriples = pBezTriple.fetchData(blenderContext.getInputStream()); + int type = this.getCurveType(curve, blenderContext); + bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); + } + curves.clear(); + result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); + } + return result; + } + + /** + * This method returns the type of the ipo curve. + * + * @param structure + * the structure must contain the 'rna_path' field and + * 'array_index' field (the type is not important here) + * @param blenderContext + * the blender context + * @return the type of the curve + */ + public int getCurveType(Structure structure, BlenderContext blenderContext) { + // reading rna path first + BlenderInputStream bis = blenderContext.getInputStream(); + int currentPosition = bis.getPosition(); + Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path"); + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress()); + bis.setPosition(dataFileBlock.getBlockPosition()); + String rnaPath = bis.readString(); + bis.setPosition(currentPosition); + int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue(); + + // determining the curve type + if (rnaPath.endsWith("location")) { + return Ipo.AC_LOC_X + arrayIndex; + } + if (rnaPath.endsWith("rotation_quaternion")) { + return Ipo.AC_QUAT_W + arrayIndex; + } + if (rnaPath.endsWith("scale")) { + return Ipo.AC_SIZE_X + arrayIndex; + } + if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) { + return Ipo.OB_ROT_X + arrayIndex; + } + LOGGER.warning("Unknown curve rna path: " + rnaPath); + return -1; + } + + /** + * This method creates an ipo with only a single value. No track type is + * specified so do not use it for calculating tracks. + * + * @param constValue + * the value of this ipo + * @return constant ipo + */ + public Ipo fromValue(float constValue) { + return new ConstIpo(constValue); + } + + @Override + public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { + return true; + } + + /** + * Ipo constant curve. This is a curve with only one value and no specified + * type. This type of ipo cannot be used to calculate tracks. It should only + * be used to calculate single value for a given frame. + * + * @author Marcin Roguski + */ + private class ConstIpo extends Ipo { + + /** The constant value of this ipo. */ + private float constValue; + + /** + * Constructor. Stores the constant value of this ipo. + * + * @param constValue + * the constant value of this ipo + */ + public ConstIpo(float constValue) { + super(null, false, 0);// the version is not important here + this.constValue = constValue; + } + + @Override + public float calculateValue(int frame) { + return constValue; + } + + @Override + public float calculateValue(int frame, int curveIndex) { + return constValue; + } + + @Override + public int getCurvesAmount() { + return 0; + } + + @Override + public BoneTrack calculateTrack(int boneIndex, Quaternion localQuaternionRotation, int startFrame, int stopFrame, int fps, boolean boneTrack) { + throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!"); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java index 4fc529ff9..044df1676 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/cameras/CameraHelper.java @@ -17,59 +17,59 @@ import java.util.logging.Logger; */ public class CameraHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(CameraHelper.class.getName()); - protected static final int DEFAULT_CAM_WIDTH = 640; - protected static final int DEFAULT_CAM_HEIGHT = 480; - + private static final Logger LOGGER = Logger.getLogger(CameraHelper.class.getName()); + protected static final int DEFAULT_CAM_WIDTH = 640; + protected static final int DEFAULT_CAM_HEIGHT = 480; + /** * This constructor parses the given blender version and stores the result. Some functionalities may differ in * different blender versions. * @param blenderVersion - * the version read from the blend file + * the version read from the blend file * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not + * a variable that indicates if the Y asxis is the UP axis or not */ public CameraHelper(String blenderVersion, boolean fixUpAxis) { super(blenderVersion, fixUpAxis); } - - /** - * This method converts the given structure to jme camera. - * - * @param structure - * camera structure - * @return jme camera object - * @throws BlenderFileException - * an exception is thrown when there are problems with the - * blender file - */ + + /** + * This method converts the given structure to jme camera. + * + * @param structure + * camera structure + * @return jme camera object + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ public CameraNode toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - if (blenderVersion >= 250) { + if (blenderVersion >= 250) { return this.toCamera250(structure, blenderContext.getSceneStructure()); } else { - return this.toCamera249(structure); + return this.toCamera249(structure); } } - /** - * This method converts the given structure to jme camera. Should be used form blender 2.5+. - * - * @param structure - * camera structure - * @param sceneStructure - * scene structure - * @return jme camera object - * @throws BlenderFileException - * an exception is thrown when there are problems with the - * blender file - */ + /** + * This method converts the given structure to jme camera. Should be used form blender 2.5+. + * + * @param structure + * camera structure + * @param sceneStructure + * scene structure + * @return jme camera object + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ private CameraNode toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException { int width = DEFAULT_CAM_WIDTH; int height = DEFAULT_CAM_HEIGHT; if (sceneStructure != null) { - Structure renderData = (Structure)sceneStructure.getFieldValue("r"); - width = ((Number)renderData.getFieldValue("xsch")).shortValue(); - height = ((Number)renderData.getFieldValue("ysch")).shortValue(); + Structure renderData = (Structure) sceneStructure.getFieldValue("r"); + width = ((Number) renderData.getFieldValue("xsch")).shortValue(); + height = ((Number) renderData.getFieldValue("ysch")).shortValue(); } Camera camera = new Camera(width, height); int type = ((Number) structure.getFieldValue("type")).intValue(); @@ -77,9 +77,9 @@ public class CameraHelper extends AbstractBlenderHelper { LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type); type = 0; } - //type==0 - perspective; type==1 - orthographic; perspective is used as default + // type==0 - perspective; type==1 - orthographic; perspective is used as default camera.setParallelProjection(type == 1); - float aspect = width / (float)height; + float aspect = width / (float) height; float fovY; // Vertical field of view in degrees float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue(); float clipend = ((Number) structure.getFieldValue("clipend")).floatValue(); @@ -88,7 +88,7 @@ public class CameraHelper extends AbstractBlenderHelper { // Default sensor size prior to 2.60 was 32. float sensor = 32.0f; boolean sensorVertical = false; - Number sensorFit = (Number)structure.getFieldValue("sensor_fit"); + Number sensorFit = (Number) structure.getFieldValue("sensor_fit"); if (sensorFit != null) { // If sensor_fit is vert (2), then sensor_y is used sensorVertical = sensorFit.byteValue() == 2; @@ -113,17 +113,17 @@ public class CameraHelper extends AbstractBlenderHelper { camera.setFrustumPerspective(fovY, aspect, clipsta, clipend); return new CameraNode(null, camera); } - + /** - * This method converts the given structure to jme camera. Should be used form blender 2.49. - * - * @param structure - * camera structure - * @return jme camera object - * @throws BlenderFileException - * an exception is thrown when there are problems with the - * blender file - */ + * This method converts the given structure to jme camera. Should be used form blender 2.49. + * + * @param structure + * camera structure + * @return jme camera object + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ private CameraNode toCamera249(Structure structure) throws BlenderFileException { Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT); int type = ((Number) structure.getFieldValue("type")).intValue(); @@ -131,7 +131,7 @@ public class CameraHelper extends AbstractBlenderHelper { LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type); type = 0; } - //type==0 - perspective; type==1 - orthographic; perspective is used as default + // type==0 - perspective; type==1 - orthographic; perspective is used as default camera.setParallelProjection(type == 1); float aspect = 0; float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue(); @@ -145,8 +145,8 @@ public class CameraHelper extends AbstractBlenderHelper { return new CameraNode(null, camera); } - @Override - public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0; - } + @Override + public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { + return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.CAMERAS) != 0; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java index 593fa5667..386242ed5 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java @@ -26,194 +26,193 @@ import com.jme3.scene.plugins.ogre.AnimData; * Constraint applied on the bone. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class BoneConstraint extends Constraint { - private static final Logger LOGGER = Logger.getLogger(BoneConstraint.class.getName()); - - protected boolean isNodeTarget; - - /** - * The bone constraint constructor. - * - * @param constraintStructure - * the constraint's structure - * @param ownerOMA - * the OMA of the bone that owns the constraint - * @param influenceIpo - * the influence interpolation curve - * @param blenderContext - * the blender context - * @throws BlenderFileException - * exception thrown when problems with blender file occur - */ - public BoneConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) - throws BlenderFileException { - super(constraintStructure, ownerOMA, influenceIpo, blenderContext); - } - - @Override - protected boolean validate() { - if(targetOMA != null) { - Spatial nodeTarget = (Spatial)blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); - //the second part of the if expression verifies if the found node (if any) is an armature node - if(nodeTarget == null || nodeTarget.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null) { - //if the target is not an object node then it is an Armature, so make sure the bone is in the current skeleton - BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); - if(targetOMA.longValue() != boneContext.getArmatureObjectOMA().longValue()) { - LOGGER.log(Level.WARNING, "Bone constraint {0} must target bone in the its own skeleton! Targeting bone in another skeleton is not supported!", name); - return false; - } - } else { - isNodeTarget = true; - } - } - - return true; - } - - @Override - public void performBakingOperation() { - Bone owner = blenderContext.getBoneContext(ownerOMA).getBone(); - - if(targetOMA != null) { - if(isNodeTarget) { - Spatial target = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); - this.prepareTracksForApplyingConstraints(); - AnimData animData = blenderContext.getAnimData(ownerOMA); - if(animData != null) { - for(Animation animation : animData.anims) { - Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); - Transform targetTransform = constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext); - - Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation); - Track targetTrack = constraintHelper.getTrack(target, animation); - - constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo); - } - } - } else { - BoneContext boneContext = blenderContext.getBoneByName(subtargetName); - Bone target = boneContext.getBone(); - this.targetOMA = boneContext.getBoneOma(); - - this.prepareTracksForApplyingConstraints(); - AnimData animData = blenderContext.getAnimData(ownerOMA); - if(animData != null) { - for(Animation animation : animData.anims) { - Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); - Transform targetTransform = constraintHelper.getBoneTransform(targetSpace, target); - - Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation); - Track targetTrack = constraintHelper.getTrack(target, animData.skeleton, animation); - - constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo); - } - } - } - } else { - this.prepareTracksForApplyingConstraints(); - AnimData animData = blenderContext.getAnimData(ownerOMA); - if(animData != null) { - for(Animation animation : animData.anims) { - Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); - Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation); - - constraintDefinition.bake(ownerTransform, null, boneTrack, null, this.ipo); - } - } - } - } - - @Override - protected void prepareTracksForApplyingConstraints() { - Long[] bonesOMAs = new Long[] { ownerOMA, targetOMA }; - Space[] spaces = new Space[] { ownerSpace, targetSpace }; - - //creating animations for current objects if at least on of their parents have an animation - for (int i = 0; i < bonesOMAs.length; ++i) { - Long oma = bonesOMAs[i]; - if(this.hasAnimation(oma)) { - Bone currentBone = blenderContext.getBoneContext(oma).getBone(); - Bone parent = currentBone.getParent(); - boolean foundAnimation = false; - AnimData animData = null; - while(parent != null && !foundAnimation) { - BoneContext boneContext = blenderContext.getBoneByName(parent.getName()); - foundAnimation = this.hasAnimation(boneContext.getBoneOma()); - animData = blenderContext.getAnimData(boneContext.getBoneOma()); - parent = parent.getParent(); - } - - if(foundAnimation) { - this.applyAnimData(blenderContext.getBoneContext(oma), spaces[i], animData); - } - } - } - - //creating animation for owner if it doesn't have one already and if the target has it - if(!this.hasAnimation(ownerOMA) && this.hasAnimation(targetOMA)) { - AnimData targetAnimData = blenderContext.getAnimData(targetOMA); - this.applyAnimData(blenderContext.getBoneContext(ownerOMA), ownerSpace, targetAnimData); - } - } - - /** - * The method determines if the bone has animations. - * - * @param animOwnerOMA - * OMA of the animation's owner - * @return true if the target has animations and false otherwise - */ - protected boolean hasAnimation(Long animOwnerOMA) { - AnimData animData = blenderContext.getAnimData(animOwnerOMA); - if(animData != null) { - if(!isNodeTarget) { - Bone bone = blenderContext.getBoneContext(animOwnerOMA).getBone(); - int boneIndex = animData.skeleton.getBoneIndex(bone); - for(Animation animation : animData.anims) { - for(Track track : animation.getTracks()) { - if(track instanceof BoneTrack && ((BoneTrack) track).getTargetBoneIndex() == boneIndex) { - return true; - } - } - } - } else { - return true; - } - } - return false; - } - - /** - * The method applies bone's current position to all of the traces of the - * given animations. - * - * @param boneContext - * the bone context - * @param space - * the bone's evaluation space - * @param referenceAnimData - * the object containing the animations - */ - protected void applyAnimData(BoneContext boneContext, Space space, AnimData referenceAnimData) { - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - Transform transform = constraintHelper.getBoneTransform(space, boneContext.getBone()); - - AnimData animData = blenderContext.getAnimData(boneContext.getBoneOma()); - - for(Animation animation : referenceAnimData.anims) { - BoneTrack parentTrack = (BoneTrack) animation.getTracks()[0]; - - float[] times = parentTrack.getTimes(); - Vector3f[] translations = new Vector3f[times.length]; - Quaternion[] rotations = new Quaternion[times.length]; - Vector3f[] scales = new Vector3f[times.length]; - Arrays.fill(translations, transform.getTranslation()); - Arrays.fill(rotations, transform.getRotation()); - Arrays.fill(scales, transform.getScale()); - for(Animation anim : animData.anims) { - anim.addTrack(new BoneTrack(animData.skeleton.getBoneIndex(boneContext.getBone()), times, translations, rotations, scales)); - } - } - blenderContext.setAnimData(boneContext.getBoneOma(), animData); - } +/* package */class BoneConstraint extends Constraint { + private static final Logger LOGGER = Logger.getLogger(BoneConstraint.class.getName()); + + protected boolean isNodeTarget; + + /** + * The bone constraint constructor. + * + * @param constraintStructure + * the constraint's structure + * @param ownerOMA + * the OMA of the bone that owns the constraint + * @param influenceIpo + * the influence interpolation curve + * @param blenderContext + * the blender context + * @throws BlenderFileException + * exception thrown when problems with blender file occur + */ + public BoneConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { + super(constraintStructure, ownerOMA, influenceIpo, blenderContext); + } + + @Override + protected boolean validate() { + if (targetOMA != null) { + Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); + // the second part of the if expression verifies if the found node (if any) is an armature node + if (nodeTarget == null || nodeTarget.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null) { + // if the target is not an object node then it is an Armature, so make sure the bone is in the current skeleton + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + if (targetOMA.longValue() != boneContext.getArmatureObjectOMA().longValue()) { + LOGGER.log(Level.WARNING, "Bone constraint {0} must target bone in the its own skeleton! Targeting bone in another skeleton is not supported!", name); + return false; + } + } else { + isNodeTarget = true; + } + } + + return true; + } + + @Override + public void performBakingOperation() { + Bone owner = blenderContext.getBoneContext(ownerOMA).getBone(); + + if (targetOMA != null) { + if (isNodeTarget) { + Spatial target = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); + this.prepareTracksForApplyingConstraints(); + AnimData animData = blenderContext.getAnimData(ownerOMA); + if (animData != null) { + for (Animation animation : animData.anims) { + Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); + Transform targetTransform = constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext); + + Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation); + Track targetTrack = constraintHelper.getTrack(target, animation); + + constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo); + } + } + } else { + BoneContext boneContext = blenderContext.getBoneByName(subtargetName); + Bone target = boneContext.getBone(); + this.targetOMA = boneContext.getBoneOma(); + + this.prepareTracksForApplyingConstraints(); + AnimData animData = blenderContext.getAnimData(ownerOMA); + if (animData != null) { + for (Animation animation : animData.anims) { + Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); + Transform targetTransform = constraintHelper.getBoneTransform(targetSpace, target); + + Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation); + Track targetTrack = constraintHelper.getTrack(target, animData.skeleton, animation); + + constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo); + } + } + } + } else { + this.prepareTracksForApplyingConstraints(); + AnimData animData = blenderContext.getAnimData(ownerOMA); + if (animData != null) { + for (Animation animation : animData.anims) { + Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); + Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation); + + constraintDefinition.bake(ownerTransform, null, boneTrack, null, this.ipo); + } + } + } + } + + @Override + protected void prepareTracksForApplyingConstraints() { + Long[] bonesOMAs = new Long[] { ownerOMA, targetOMA }; + Space[] spaces = new Space[] { ownerSpace, targetSpace }; + + // creating animations for current objects if at least on of their parents have an animation + for (int i = 0; i < bonesOMAs.length; ++i) { + Long oma = bonesOMAs[i]; + if (this.hasAnimation(oma)) { + Bone currentBone = blenderContext.getBoneContext(oma).getBone(); + Bone parent = currentBone.getParent(); + boolean foundAnimation = false; + AnimData animData = null; + while (parent != null && !foundAnimation) { + BoneContext boneContext = blenderContext.getBoneByName(parent.getName()); + foundAnimation = this.hasAnimation(boneContext.getBoneOma()); + animData = blenderContext.getAnimData(boneContext.getBoneOma()); + parent = parent.getParent(); + } + + if (foundAnimation) { + this.applyAnimData(blenderContext.getBoneContext(oma), spaces[i], animData); + } + } + } + + // creating animation for owner if it doesn't have one already and if the target has it + if (!this.hasAnimation(ownerOMA) && this.hasAnimation(targetOMA)) { + AnimData targetAnimData = blenderContext.getAnimData(targetOMA); + this.applyAnimData(blenderContext.getBoneContext(ownerOMA), ownerSpace, targetAnimData); + } + } + + /** + * The method determines if the bone has animations. + * + * @param animOwnerOMA + * OMA of the animation's owner + * @return true if the target has animations and false otherwise + */ + protected boolean hasAnimation(Long animOwnerOMA) { + AnimData animData = blenderContext.getAnimData(animOwnerOMA); + if (animData != null) { + if (!isNodeTarget) { + Bone bone = blenderContext.getBoneContext(animOwnerOMA).getBone(); + int boneIndex = animData.skeleton.getBoneIndex(bone); + for (Animation animation : animData.anims) { + for (Track track : animation.getTracks()) { + if (track instanceof BoneTrack && ((BoneTrack) track).getTargetBoneIndex() == boneIndex) { + return true; + } + } + } + } else { + return true; + } + } + return false; + } + + /** + * The method applies bone's current position to all of the traces of the + * given animations. + * + * @param boneContext + * the bone context + * @param space + * the bone's evaluation space + * @param referenceAnimData + * the object containing the animations + */ + protected void applyAnimData(BoneContext boneContext, Space space, AnimData referenceAnimData) { + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + Transform transform = constraintHelper.getBoneTransform(space, boneContext.getBone()); + + AnimData animData = blenderContext.getAnimData(boneContext.getBoneOma()); + + for (Animation animation : referenceAnimData.anims) { + BoneTrack parentTrack = (BoneTrack) animation.getTracks()[0]; + + float[] times = parentTrack.getTimes(); + Vector3f[] translations = new Vector3f[times.length]; + Quaternion[] rotations = new Quaternion[times.length]; + Vector3f[] scales = new Vector3f[times.length]; + Arrays.fill(translations, transform.getTranslation()); + Arrays.fill(rotations, transform.getRotation()); + Arrays.fill(scales, transform.getScale()); + for (Animation anim : animData.anims) { + anim.addTrack(new BoneTrack(animData.skeleton.getBoneIndex(boneContext.getBone()), times, translations, rotations, scales)); + } + } + blenderContext.setAnimData(boneContext.getBoneOma(), animData); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java index 07792e652..7a883c8d0 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java @@ -19,109 +19,109 @@ import com.jme3.scene.plugins.blender.file.Structure; * @author Marcin Roguski (Kaelthas) */ public abstract class Constraint { - private static final Logger LOGGER = Logger.getLogger(Constraint.class.getName()); - - /** The name of this constraint. */ - protected final String name; - /** Indicates if the constraint is already baked or not. */ - protected boolean baked; - - protected Space ownerSpace; - protected final ConstraintDefinition constraintDefinition; - protected Long ownerOMA; - - protected Long targetOMA; - protected Space targetSpace; - protected String subtargetName; - - /** The ipo object defining influence. */ - protected final Ipo ipo; - /** The blender context. */ - protected final BlenderContext blenderContext; - protected final ConstraintHelper constraintHelper; - - /** - * This constructor creates the constraint instance. - * - * @param constraintStructure - * the constraint's structure (bConstraint clss in blender 2.49). - * @param ownerOMA - * the old memory address of the constraint owner - * @param influenceIpo - * the ipo curve of the influence factor - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public Constraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { - this.blenderContext = blenderContext; - this.name = constraintStructure.getFieldValue("name").toString(); - Pointer pData = (Pointer) constraintStructure.getFieldValue("data"); - if (pData.isNotNull()) { - Structure data = pData.fetchData(blenderContext.getInputStream()).get(0); - constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, blenderContext); - Pointer pTar = (Pointer)data.getFieldValue("tar"); - if(pTar!= null && pTar.isNotNull()) { - this.targetOMA = pTar.getOldMemoryAddress(); - this.targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue()); - Object subtargetValue = data.getFieldValue("subtarget"); - if(subtargetValue != null) {//not all constraint data have the subtarget field - subtargetName = subtargetValue.toString(); - } - } - } else { - //Null constraint has no data, so create it here - constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, blenderContext); - } - this.ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue()); - this.ipo = influenceIpo; - this.ownerOMA = ownerOMA; - this.constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - } - - /** - * This method bakes the required sontraints into its owner. It checks if the constraint is invalid - * or if it isn't yet baked. It also performs baking of its target constraints so that the proper baking - * order is kept. - */ - public void bake() { - if(!this.validate()) { - LOGGER.warning("The constraint " + name + " is invalid and will not be applied."); - } else if(!baked) { - if(targetOMA != null) { - List targetConstraints = blenderContext.getConstraints(targetOMA); - if(targetConstraints != null && targetConstraints.size() > 0) { - LOGGER.log(Level.FINE, "Baking target constraints of constraint: {0}", name); - for(Constraint targetConstraint : targetConstraints) { - targetConstraint.bake(); - } - } - } - - LOGGER.log(Level.FINE, "Performing baking of constraint: {0}", name); - this.performBakingOperation(); - baked = true; - } - } - - /** - * Performs validation before baking. Checks factors that can prevent constraint from baking that could not be - * checked during constraint loading. - */ - protected abstract boolean validate(); - - /** - * This method should be overwridden and perform the baking opertion. - */ - protected abstract void performBakingOperation(); - - /** - * This method prepares the tracks for both owner and parent. If either owner or parent have no track while its parent has - - * the tracks are created. The tracks will not modify the owner/target movement but will be there ready for applying constraints. - * For example if the owner is a spatial and has no animation but its parent is moving then the track is created for the owner - * that will have non modifying values for translation, rotation and scale and will have the same amount of frames as its parent has. - */ - protected abstract void prepareTracksForApplyingConstraints(); + private static final Logger LOGGER = Logger.getLogger(Constraint.class.getName()); + + /** The name of this constraint. */ + protected final String name; + /** Indicates if the constraint is already baked or not. */ + protected boolean baked; + + protected Space ownerSpace; + protected final ConstraintDefinition constraintDefinition; + protected Long ownerOMA; + + protected Long targetOMA; + protected Space targetSpace; + protected String subtargetName; + + /** The ipo object defining influence. */ + protected final Ipo ipo; + /** The blender context. */ + protected final BlenderContext blenderContext; + protected final ConstraintHelper constraintHelper; + + /** + * This constructor creates the constraint instance. + * + * @param constraintStructure + * the constraint's structure (bConstraint clss in blender 2.49). + * @param ownerOMA + * the old memory address of the constraint owner + * @param influenceIpo + * the ipo curve of the influence factor + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Constraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { + this.blenderContext = blenderContext; + this.name = constraintStructure.getFieldValue("name").toString(); + Pointer pData = (Pointer) constraintStructure.getFieldValue("data"); + if (pData.isNotNull()) { + Structure data = pData.fetchData(blenderContext.getInputStream()).get(0); + constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, blenderContext); + Pointer pTar = (Pointer) data.getFieldValue("tar"); + if (pTar != null && pTar.isNotNull()) { + this.targetOMA = pTar.getOldMemoryAddress(); + this.targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue()); + Object subtargetValue = data.getFieldValue("subtarget"); + if (subtargetValue != null) {// not all constraint data have the subtarget field + subtargetName = subtargetValue.toString(); + } + } + } else { + // Null constraint has no data, so create it here + constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, blenderContext); + } + this.ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue()); + this.ipo = influenceIpo; + this.ownerOMA = ownerOMA; + this.constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + } + + /** + * This method bakes the required sontraints into its owner. It checks if the constraint is invalid + * or if it isn't yet baked. It also performs baking of its target constraints so that the proper baking + * order is kept. + */ + public void bake() { + if (!this.validate()) { + LOGGER.warning("The constraint " + name + " is invalid and will not be applied."); + } else if (!baked) { + if (targetOMA != null) { + List targetConstraints = blenderContext.getConstraints(targetOMA); + if (targetConstraints != null && targetConstraints.size() > 0) { + LOGGER.log(Level.FINE, "Baking target constraints of constraint: {0}", name); + for (Constraint targetConstraint : targetConstraints) { + targetConstraint.bake(); + } + } + } + + LOGGER.log(Level.FINE, "Performing baking of constraint: {0}", name); + this.performBakingOperation(); + baked = true; + } + } + + /** + * Performs validation before baking. Checks factors that can prevent constraint from baking that could not be + * checked during constraint loading. + */ + protected abstract boolean validate(); + + /** + * This method should be overwridden and perform the baking opertion. + */ + protected abstract void performBakingOperation(); + + /** + * This method prepares the tracks for both owner and parent. If either owner or parent have no track while its parent has - + * the tracks are created. The tracks will not modify the owner/target movement but will be there ready for applying constraints. + * For example if the owner is a spatial and has no animation but its parent is moving then the track is created for the owner + * that will have non modifying values for translation, rotation and scale and will have the same amount of frames as its parent has. + */ + protected abstract void prepareTracksForApplyingConstraints(); } \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java index af06ae44a..6ecef8cc1 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java @@ -32,444 +32,444 @@ import com.jme3.scene.plugins.blender.file.Structure; * @author Marcin Roguski (Kaelthas) */ public class ConstraintHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName()); - - /** - * Helper constructor. It's main task is to generate the affection functions. These functions are common to all - * ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall - * consider refactoring. The constructor parses the given blender version and stores the result. Some - * functionalities may differ in different blender versions. - * @param blenderVersion - * the version read from the blend file - * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not - */ - public ConstraintHelper(String blenderVersion, BlenderContext blenderContext, boolean fixUpAxis) { - super(blenderVersion, fixUpAxis); - } - - /** - * This method reads constraints for for the given structure. The - * constraints are loaded only once for object/bone. - * - * @param objectStructure - * the structure we read constraint's for - * @param blenderContext - * the blender context - * @throws BlenderFileException - */ - public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.fine("Loading constraints."); - // reading influence ipos for the constraints - IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); - Map> constraintsIpos = new HashMap>(); - Pointer pActions = (Pointer) objectStructure.getFieldValue("action"); - if (pActions.isNotNull()) { - List actions = pActions.fetchData(blenderContext.getInputStream()); - for (Structure action : actions) { - Structure chanbase = (Structure) action.getFieldValue("chanbase"); - List actionChannels = chanbase.evaluateListBase(blenderContext); - for (Structure actionChannel : actionChannels) { - Map ipos = new HashMap(); - Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels"); - List constraintChannels = constChannels.evaluateListBase(blenderContext); - for (Structure constraintChannel : constraintChannels) { - Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo"); - if (pIpo.isNotNull()) { - String constraintName = constraintChannel.getFieldValue("name").toString(); - Ipo ipo = ipoHelper.fromIpoStructure(pIpo.fetchData(blenderContext.getInputStream()).get(0), blenderContext); - ipos.put(constraintName, ipo); - } - } - String actionName = actionChannel.getFieldValue("name").toString(); - constraintsIpos.put(actionName, ipos); - } - } - } - - //loading constraints connected with the object's bones - Pointer pPose = (Pointer) objectStructure.getFieldValue("pose"); - if (pPose.isNotNull()) { - List poseChannels = ((Structure) pPose.fetchData(blenderContext.getInputStream()).get(0).getFieldValue("chanbase")).evaluateListBase(blenderContext); - for (Structure poseChannel : poseChannels) { - List constraintsList = new ArrayList(); - Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress()); - - //the name is read directly from structure because bone might not yet be loaded - String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString(); - List constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext); - for (Structure constraint : constraints) { - String constraintName = constraint.getFieldValue("name").toString(); - Map ipoMap = constraintsIpos.get(name); - Ipo ipo = ipoMap==null ? null : ipoMap.get(constraintName); - if (ipo == null) { - float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); - ipo = ipoHelper.fromValue(enforce); - } - constraintsList.add(new BoneConstraint(constraint, boneOMA, ipo, blenderContext)); - } - blenderContext.addConstraints(boneOMA, constraintsList); - } - } - - //loading constraints connected with the object itself - List constraints = ((Structure)objectStructure.getFieldValue("constraints")).evaluateListBase(blenderContext); - if(constraints != null && constraints.size() > 0) { - Pointer pData = (Pointer) objectStructure.getFieldValue("data"); - String dataType = pData.isNotNull() ? pData.fetchData(blenderContext.getInputStream()).get(0).getType() : null; - List constraintsList = new ArrayList(constraints.size()); - - for(Structure constraint : constraints) { - String constraintName = constraint.getFieldValue("name").toString(); - String objectName = objectStructure.getName(); - - Map objectConstraintsIpos = constraintsIpos.get(objectName); - Ipo ipo = objectConstraintsIpos!=null ? objectConstraintsIpos.get(constraintName) : null; - if (ipo == null) { - float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); - ipo = ipoHelper.fromValue(enforce); - } - - constraintsList.add(this.getConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext)); - } - blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList); - } - } - - /** - * This method creates a proper constraint object depending on the object's - * data type. Supported data types:
  • Mesh
  • Armature
  • Camera
  • - * Lamp Bone constraints are created in a different place. - * - * @param dataType - * the type of the object's data - * @param constraintStructure - * the constraint structure - * @param ownerOMA - * the owner OMA - * @param influenceIpo - * the influence interpolation curve - * @param blenderContext - * the blender context - * @return constraint object for the required type - * @throws BlenderFileException - * thrown when problems with blender file occured - */ - private Constraint getConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { - if(dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) { - return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); - } else if("Armature".equalsIgnoreCase(dataType)) { - return new SkeletonConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); - } else { - throw new IllegalArgumentException("Unsupported data type for applying constraints: " + dataType); - } - } - - /** - * The method bakes all available and valid constraints. - * - * @param blenderContext - * the blender context - */ - public void bakeConstraints(BlenderContext blenderContext) { - for(Constraint constraint : blenderContext.getAllConstraints()) { - constraint.bake(); - } - } - - /** - * The method returns track for bone. - * - * @param bone - * the bone - * @param skeleton - * the bone's skeleton - * @param animation - * the bone's animation - * @return track for the given bone that was found among the given - * animations or null if none is found - */ - /*package*/ BoneTrack getTrack(Bone bone, Skeleton skeleton, Animation animation) { - int boneIndex = skeleton.getBoneIndex(bone); - for (Track track : animation.getTracks()) { + private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName()); + + /** + * Helper constructor. It's main task is to generate the affection functions. These functions are common to all + * ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall + * consider refactoring. The constructor parses the given blender version and stores the result. Some + * functionalities may differ in different blender versions. + * @param blenderVersion + * the version read from the blend file + * @param fixUpAxis + * a variable that indicates if the Y asxis is the UP axis or not + */ + public ConstraintHelper(String blenderVersion, BlenderContext blenderContext, boolean fixUpAxis) { + super(blenderVersion, fixUpAxis); + } + + /** + * This method reads constraints for for the given structure. The + * constraints are loaded only once for object/bone. + * + * @param objectStructure + * the structure we read constraint's for + * @param blenderContext + * the blender context + * @throws BlenderFileException + */ + public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.fine("Loading constraints."); + // reading influence ipos for the constraints + IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); + Map> constraintsIpos = new HashMap>(); + Pointer pActions = (Pointer) objectStructure.getFieldValue("action"); + if (pActions.isNotNull()) { + List actions = pActions.fetchData(blenderContext.getInputStream()); + for (Structure action : actions) { + Structure chanbase = (Structure) action.getFieldValue("chanbase"); + List actionChannels = chanbase.evaluateListBase(blenderContext); + for (Structure actionChannel : actionChannels) { + Map ipos = new HashMap(); + Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels"); + List constraintChannels = constChannels.evaluateListBase(blenderContext); + for (Structure constraintChannel : constraintChannels) { + Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo"); + if (pIpo.isNotNull()) { + String constraintName = constraintChannel.getFieldValue("name").toString(); + Ipo ipo = ipoHelper.fromIpoStructure(pIpo.fetchData(blenderContext.getInputStream()).get(0), blenderContext); + ipos.put(constraintName, ipo); + } + } + String actionName = actionChannel.getFieldValue("name").toString(); + constraintsIpos.put(actionName, ipos); + } + } + } + + // loading constraints connected with the object's bones + Pointer pPose = (Pointer) objectStructure.getFieldValue("pose"); + if (pPose.isNotNull()) { + List poseChannels = ((Structure) pPose.fetchData(blenderContext.getInputStream()).get(0).getFieldValue("chanbase")).evaluateListBase(blenderContext); + for (Structure poseChannel : poseChannels) { + List constraintsList = new ArrayList(); + Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress()); + + // the name is read directly from structure because bone might not yet be loaded + String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString(); + List constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext); + for (Structure constraint : constraints) { + String constraintName = constraint.getFieldValue("name").toString(); + Map ipoMap = constraintsIpos.get(name); + Ipo ipo = ipoMap == null ? null : ipoMap.get(constraintName); + if (ipo == null) { + float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); + ipo = ipoHelper.fromValue(enforce); + } + constraintsList.add(new BoneConstraint(constraint, boneOMA, ipo, blenderContext)); + } + blenderContext.addConstraints(boneOMA, constraintsList); + } + } + + // loading constraints connected with the object itself + List constraints = ((Structure) objectStructure.getFieldValue("constraints")).evaluateListBase(blenderContext); + if (constraints != null && constraints.size() > 0) { + Pointer pData = (Pointer) objectStructure.getFieldValue("data"); + String dataType = pData.isNotNull() ? pData.fetchData(blenderContext.getInputStream()).get(0).getType() : null; + List constraintsList = new ArrayList(constraints.size()); + + for (Structure constraint : constraints) { + String constraintName = constraint.getFieldValue("name").toString(); + String objectName = objectStructure.getName(); + + Map objectConstraintsIpos = constraintsIpos.get(objectName); + Ipo ipo = objectConstraintsIpos != null ? objectConstraintsIpos.get(constraintName) : null; + if (ipo == null) { + float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); + ipo = ipoHelper.fromValue(enforce); + } + + constraintsList.add(this.getConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext)); + } + blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList); + } + } + + /** + * This method creates a proper constraint object depending on the object's + * data type. Supported data types:
  • Mesh
  • Armature
  • Camera
  • + * Lamp Bone constraints are created in a different place. + * + * @param dataType + * the type of the object's data + * @param constraintStructure + * the constraint structure + * @param ownerOMA + * the owner OMA + * @param influenceIpo + * the influence interpolation curve + * @param blenderContext + * the blender context + * @return constraint object for the required type + * @throws BlenderFileException + * thrown when problems with blender file occured + */ + private Constraint getConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { + if (dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) { + return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); + } else if ("Armature".equalsIgnoreCase(dataType)) { + return new SkeletonConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); + } else { + throw new IllegalArgumentException("Unsupported data type for applying constraints: " + dataType); + } + } + + /** + * The method bakes all available and valid constraints. + * + * @param blenderContext + * the blender context + */ + public void bakeConstraints(BlenderContext blenderContext) { + for (Constraint constraint : blenderContext.getAllConstraints()) { + constraint.bake(); + } + } + + /** + * The method returns track for bone. + * + * @param bone + * the bone + * @param skeleton + * the bone's skeleton + * @param animation + * the bone's animation + * @return track for the given bone that was found among the given + * animations or null if none is found + */ + /* package */BoneTrack getTrack(Bone bone, Skeleton skeleton, Animation animation) { + int boneIndex = skeleton.getBoneIndex(bone); + for (Track track : animation.getTracks()) { if (((BoneTrack) track).getTargetBoneIndex() == boneIndex) { return (BoneTrack) track; } } - return null; - } - - /** - * The method returns track for spatial. - * - * @param bone - * the spatial - * @param animation - * the spatial's animation - * @return track for the given spatial that was found among the given - * animations or null if none is found - */ - /*package*/ SpatialTrack getTrack(Spatial spatial, Animation animation) { - Track[] tracks = animation.getTracks(); - if(tracks != null && tracks.length == 1) { - return (SpatialTrack)tracks[0]; - } - return null; - } - - /** - * This method returns the transform read directly from the blender - * structure. This can be used to read transforms from one of the object - * types:
  • Spatial
  • Camera
  • Light - * - * @param space - * the space where transform is evaluated - * @param spatialOMA - * the OMA of the object - * @param blenderContext - * the blender context - * @return the object's transform in a given space - */ - @SuppressWarnings("unchecked") - /*package*/ Transform getNodeObjectTransform(Space space, Long spatialOMA, BlenderContext blenderContext) { - switch (space) { - case CONSTRAINT_SPACE_LOCAL: - Structure targetStructure = (Structure) blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_STRUCTURE); - - DynamicArray locArray = ((DynamicArray) targetStructure.getFieldValue("loc")); - Vector3f loc = new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), locArray.get(2).floatValue()); - DynamicArray rotArray = ((DynamicArray) targetStructure.getFieldValue("rot")); - Quaternion rot = new Quaternion(new float[] { rotArray.get(0).floatValue(), rotArray.get(1).floatValue(), rotArray.get(2).floatValue() }); - DynamicArray sizeArray = ((DynamicArray) targetStructure.getFieldValue("size")); - Vector3f size = new Vector3f(sizeArray.get(0).floatValue(), sizeArray.get(1).floatValue(), sizeArray.get(2).floatValue()); - - if (blenderContext.getBlenderKey().isFixUpAxis()) { - float y = loc.y; - loc.y = loc.z; - loc.z = -y; - - y = rot.getY(); - float z = rot.getZ(); - rot.set(rot.getX(), z, -y, rot.getW()); - - y = size.y; - size.y = size.z; - size.z = y; - } - - Transform result = new Transform(loc, rot); - result.setScale(size); - return result; - case CONSTRAINT_SPACE_WORLD://TODO: get it from the object structure ??? - Object feature = blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_FEATURE); - if(feature instanceof Spatial) { - return ((Spatial) feature).getWorldTransform(); - } else if(feature instanceof Skeleton) { - LOGGER.warning("Trying to get transformation for skeleton. This is not supported. Returning null."); - return null; - } else { - throw new IllegalArgumentException("Given old memory address does not point to a valid object type (spatial, camera or light)."); - } - default: - throw new IllegalStateException("Invalid space type for target object: " + space.toString()); - } - } - - /** - * The method returns the transform for the given bone computed in the given - * space. - * - * @param space - * the computation space - * @param bone - * the bone we get the transform from - * @return the transform of the given bone - */ - /*package*/ Transform getBoneTransform(Space space, Bone bone) { - switch (space) { - case CONSTRAINT_SPACE_LOCAL: - Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); - localTransform.setScale(bone.getLocalScale()); - return localTransform; - case CONSTRAINT_SPACE_WORLD: - Transform worldTransform = new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation()); - worldTransform.setScale(bone.getWorldBindScale()); - return worldTransform; - case CONSTRAINT_SPACE_POSE: - Transform poseTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); - poseTransform.setScale(bone.getLocalScale()); - return poseTransform; - case CONSTRAINT_SPACE_PARLOCAL: - Transform parentLocalTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); - parentLocalTransform.setScale(bone.getLocalScale()); - return parentLocalTransform; - default: - throw new IllegalStateException("Invalid space type for target object: " + space.toString()); - } - } - - /** - * The method applies the transform for the given spatial, computed in the - * given space. - * - * @param spatial - * the spatial we apply the transform for - * @param space - * the computation space - * @param transform - * the transform being applied - */ - /*package*/ void applyTransform(Spatial spatial, Space space, Transform transform) { - switch (space) { - case CONSTRAINT_SPACE_LOCAL: - Transform ownerLocalTransform = spatial.getLocalTransform(); - ownerLocalTransform.getTranslation().addLocal(transform.getTranslation()); - ownerLocalTransform.getRotation().multLocal(transform.getRotation()); - ownerLocalTransform.getScale().multLocal(transform.getScale()); - break; - case CONSTRAINT_SPACE_WORLD: - Matrix4f m = this.getParentWorldTransformMatrix(spatial); - m.invertLocal(); - Matrix4f matrix = this.toMatrix(transform); - m.multLocal(matrix); - - float scaleX = (float) Math.sqrt(m.m00 * m.m00 + m.m10 * m.m10 + m.m20 * m.m20); - float scaleY = (float) Math.sqrt(m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21); - float scaleZ = (float) Math.sqrt(m.m02 * m.m02 + m.m12 * m.m12 + m.m22 * m.m22); - - transform.setTranslation(m.toTranslationVector()); - transform.setRotation(m.toRotationQuat()); - transform.setScale(scaleX, scaleY, scaleZ); - spatial.setLocalTransform(transform); - break; - case CONSTRAINT_SPACE_PARLOCAL: - case CONSTRAINT_SPACE_POSE: - throw new IllegalStateException("Invalid space type (" + space.toString() + ") for owner object."); - default: - throw new IllegalStateException("Invalid space type for target object: " + space.toString()); - } - } - - /** - * The method applies the transform for the given bone, computed in the - * given space. - * - * @param bone - * the bone we apply the transform for - * @param space - * the computation space - * @param transform - * the transform being applied - */ - /*package*/ void applyTransform(Bone bone, Space space, Transform transform) { - switch (space) { - case CONSTRAINT_SPACE_LOCAL: - bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); - break; - case CONSTRAINT_SPACE_WORLD: - Matrix4f m = this.getParentWorldTransformMatrix(bone); - // m.invertLocal(); - transform.setTranslation(m.mult(transform.getTranslation())); - transform.setRotation(m.mult(transform.getRotation(), null)); - transform.setScale(transform.getScale()); - bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); - // float x = FastMath.HALF_PI/2; - // float y = -FastMath.HALF_PI; - // float z = -FastMath.HALF_PI/2; - // bone.setBindTransforms(new Vector3f(0,0,0), new Quaternion().fromAngles(x, y, z), new Vector3f(1,1,1)); - break; - case CONSTRAINT_SPACE_PARLOCAL: - Vector3f parentLocalTranslation = bone.getLocalPosition().add(transform.getTranslation()); - Quaternion parentLocalRotation = bone.getLocalRotation().mult(transform.getRotation()); - bone.setBindTransforms(parentLocalTranslation, parentLocalRotation, transform.getScale()); - break; - case CONSTRAINT_SPACE_POSE: - bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); - break; - default: - throw new IllegalStateException("Invalid space type for target object: " + space.toString()); - } - } - - /** - * @return world transform matrix of the feature's parent or identity matrix - * if the feature has no parent - */ - private Matrix4f getParentWorldTransformMatrix(Spatial spatial) { - Matrix4f result = new Matrix4f(); - if (spatial.getParent() != null) { - Transform t = spatial.getParent().getWorldTransform(); - result.setTransform(t.getTranslation(), t.getScale(), t.getRotation().toRotationMatrix()); - } - return result; - } - - /** - * @return world transform matrix of the feature's parent or identity matrix - * if the feature has no parent - */ - private Matrix4f getParentWorldTransformMatrix(Bone bone) { - Matrix4f result = new Matrix4f(); - Bone parent = bone.getParent(); - if (parent != null) { - result.setTransform(parent.getWorldBindPosition(), parent.getWorldBindScale(), parent.getWorldBindRotation().toRotationMatrix()); - } - return result; - } - - /** - * Converts given transform to the matrix. - * - * @param transform - * the transform to be converted - * @return 4x4 matri that represents the given transform - */ - private Matrix4f toMatrix(Transform transform) { - Matrix4f result = Matrix4f.IDENTITY; - if (transform != null) { - result = new Matrix4f(); - result.setTranslation(transform.getTranslation()); - result.setRotationQuaternion(transform.getRotation()); - result.setScale(transform.getScale()); - } - return result; - } - - @Override - public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - return true; - } - - /** - * The space of target or owner transformation. - * - * @author Marcin Roguski (Kaelthas) - */ - public static enum Space { - - CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_POSE, CONSTRAINT_SPACE_PARLOCAL, CONSTRAINT_SPACE_INVALID; - - /** - * This method returns the enum instance when given the appropriate - * value from the blend file. - * - * @param c - * the blender's value of the space modifier - * @return the scape enum instance - */ - public static Space valueOf(byte c) { - switch (c) { - case 0: - return CONSTRAINT_SPACE_WORLD; - case 1: - return CONSTRAINT_SPACE_LOCAL; - case 2: - return CONSTRAINT_SPACE_POSE; - case 3: - return CONSTRAINT_SPACE_PARLOCAL; - default: - return CONSTRAINT_SPACE_INVALID; - } - } - } + return null; + } + + /** + * The method returns track for spatial. + * + * @param bone + * the spatial + * @param animation + * the spatial's animation + * @return track for the given spatial that was found among the given + * animations or null if none is found + */ + /* package */SpatialTrack getTrack(Spatial spatial, Animation animation) { + Track[] tracks = animation.getTracks(); + if (tracks != null && tracks.length == 1) { + return (SpatialTrack) tracks[0]; + } + return null; + } + + /** + * This method returns the transform read directly from the blender + * structure. This can be used to read transforms from one of the object + * types:
  • Spatial
  • Camera
  • Light + * + * @param space + * the space where transform is evaluated + * @param spatialOMA + * the OMA of the object + * @param blenderContext + * the blender context + * @return the object's transform in a given space + */ + @SuppressWarnings("unchecked") + /* package */Transform getNodeObjectTransform(Space space, Long spatialOMA, BlenderContext blenderContext) { + switch (space) { + case CONSTRAINT_SPACE_LOCAL: + Structure targetStructure = (Structure) blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_STRUCTURE); + + DynamicArray locArray = ((DynamicArray) targetStructure.getFieldValue("loc")); + Vector3f loc = new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), locArray.get(2).floatValue()); + DynamicArray rotArray = ((DynamicArray) targetStructure.getFieldValue("rot")); + Quaternion rot = new Quaternion(new float[] { rotArray.get(0).floatValue(), rotArray.get(1).floatValue(), rotArray.get(2).floatValue() }); + DynamicArray sizeArray = ((DynamicArray) targetStructure.getFieldValue("size")); + Vector3f size = new Vector3f(sizeArray.get(0).floatValue(), sizeArray.get(1).floatValue(), sizeArray.get(2).floatValue()); + + if (blenderContext.getBlenderKey().isFixUpAxis()) { + float y = loc.y; + loc.y = loc.z; + loc.z = -y; + + y = rot.getY(); + float z = rot.getZ(); + rot.set(rot.getX(), z, -y, rot.getW()); + + y = size.y; + size.y = size.z; + size.z = y; + } + + Transform result = new Transform(loc, rot); + result.setScale(size); + return result; + case CONSTRAINT_SPACE_WORLD:// TODO: get it from the object structure ??? + Object feature = blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_FEATURE); + if (feature instanceof Spatial) { + return ((Spatial) feature).getWorldTransform(); + } else if (feature instanceof Skeleton) { + LOGGER.warning("Trying to get transformation for skeleton. This is not supported. Returning null."); + return null; + } else { + throw new IllegalArgumentException("Given old memory address does not point to a valid object type (spatial, camera or light)."); + } + default: + throw new IllegalStateException("Invalid space type for target object: " + space.toString()); + } + } + + /** + * The method returns the transform for the given bone computed in the given + * space. + * + * @param space + * the computation space + * @param bone + * the bone we get the transform from + * @return the transform of the given bone + */ + /* package */Transform getBoneTransform(Space space, Bone bone) { + switch (space) { + case CONSTRAINT_SPACE_LOCAL: + Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); + localTransform.setScale(bone.getLocalScale()); + return localTransform; + case CONSTRAINT_SPACE_WORLD: + Transform worldTransform = new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation()); + worldTransform.setScale(bone.getWorldBindScale()); + return worldTransform; + case CONSTRAINT_SPACE_POSE: + Transform poseTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); + poseTransform.setScale(bone.getLocalScale()); + return poseTransform; + case CONSTRAINT_SPACE_PARLOCAL: + Transform parentLocalTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); + parentLocalTransform.setScale(bone.getLocalScale()); + return parentLocalTransform; + default: + throw new IllegalStateException("Invalid space type for target object: " + space.toString()); + } + } + + /** + * The method applies the transform for the given spatial, computed in the + * given space. + * + * @param spatial + * the spatial we apply the transform for + * @param space + * the computation space + * @param transform + * the transform being applied + */ + /* package */void applyTransform(Spatial spatial, Space space, Transform transform) { + switch (space) { + case CONSTRAINT_SPACE_LOCAL: + Transform ownerLocalTransform = spatial.getLocalTransform(); + ownerLocalTransform.getTranslation().addLocal(transform.getTranslation()); + ownerLocalTransform.getRotation().multLocal(transform.getRotation()); + ownerLocalTransform.getScale().multLocal(transform.getScale()); + break; + case CONSTRAINT_SPACE_WORLD: + Matrix4f m = this.getParentWorldTransformMatrix(spatial); + m.invertLocal(); + Matrix4f matrix = this.toMatrix(transform); + m.multLocal(matrix); + + float scaleX = (float) Math.sqrt(m.m00 * m.m00 + m.m10 * m.m10 + m.m20 * m.m20); + float scaleY = (float) Math.sqrt(m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21); + float scaleZ = (float) Math.sqrt(m.m02 * m.m02 + m.m12 * m.m12 + m.m22 * m.m22); + + transform.setTranslation(m.toTranslationVector()); + transform.setRotation(m.toRotationQuat()); + transform.setScale(scaleX, scaleY, scaleZ); + spatial.setLocalTransform(transform); + break; + case CONSTRAINT_SPACE_PARLOCAL: + case CONSTRAINT_SPACE_POSE: + throw new IllegalStateException("Invalid space type (" + space.toString() + ") for owner object."); + default: + throw new IllegalStateException("Invalid space type for target object: " + space.toString()); + } + } + + /** + * The method applies the transform for the given bone, computed in the + * given space. + * + * @param bone + * the bone we apply the transform for + * @param space + * the computation space + * @param transform + * the transform being applied + */ + /* package */void applyTransform(Bone bone, Space space, Transform transform) { + switch (space) { + case CONSTRAINT_SPACE_LOCAL: + bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); + break; + case CONSTRAINT_SPACE_WORLD: + Matrix4f m = this.getParentWorldTransformMatrix(bone); + // m.invertLocal(); + transform.setTranslation(m.mult(transform.getTranslation())); + transform.setRotation(m.mult(transform.getRotation(), null)); + transform.setScale(transform.getScale()); + bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); + // float x = FastMath.HALF_PI/2; + // float y = -FastMath.HALF_PI; + // float z = -FastMath.HALF_PI/2; + // bone.setBindTransforms(new Vector3f(0,0,0), new Quaternion().fromAngles(x, y, z), new Vector3f(1,1,1)); + break; + case CONSTRAINT_SPACE_PARLOCAL: + Vector3f parentLocalTranslation = bone.getLocalPosition().add(transform.getTranslation()); + Quaternion parentLocalRotation = bone.getLocalRotation().mult(transform.getRotation()); + bone.setBindTransforms(parentLocalTranslation, parentLocalRotation, transform.getScale()); + break; + case CONSTRAINT_SPACE_POSE: + bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); + break; + default: + throw new IllegalStateException("Invalid space type for target object: " + space.toString()); + } + } + + /** + * @return world transform matrix of the feature's parent or identity matrix + * if the feature has no parent + */ + private Matrix4f getParentWorldTransformMatrix(Spatial spatial) { + Matrix4f result = new Matrix4f(); + if (spatial.getParent() != null) { + Transform t = spatial.getParent().getWorldTransform(); + result.setTransform(t.getTranslation(), t.getScale(), t.getRotation().toRotationMatrix()); + } + return result; + } + + /** + * @return world transform matrix of the feature's parent or identity matrix + * if the feature has no parent + */ + private Matrix4f getParentWorldTransformMatrix(Bone bone) { + Matrix4f result = new Matrix4f(); + Bone parent = bone.getParent(); + if (parent != null) { + result.setTransform(parent.getWorldBindPosition(), parent.getWorldBindScale(), parent.getWorldBindRotation().toRotationMatrix()); + } + return result; + } + + /** + * Converts given transform to the matrix. + * + * @param transform + * the transform to be converted + * @return 4x4 matri that represents the given transform + */ + private Matrix4f toMatrix(Transform transform) { + Matrix4f result = Matrix4f.IDENTITY; + if (transform != null) { + result = new Matrix4f(); + result.setTranslation(transform.getTranslation()); + result.setRotationQuaternion(transform.getRotation()); + result.setScale(transform.getScale()); + } + return result; + } + + @Override + public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { + return true; + } + + /** + * The space of target or owner transformation. + * + * @author Marcin Roguski (Kaelthas) + */ + public static enum Space { + + CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_POSE, CONSTRAINT_SPACE_PARLOCAL, CONSTRAINT_SPACE_INVALID; + + /** + * This method returns the enum instance when given the appropriate + * value from the blend file. + * + * @param c + * the blender's value of the space modifier + * @return the scape enum instance + */ + public static Space valueOf(byte c) { + switch (c) { + case 0: + return CONSTRAINT_SPACE_WORLD; + case 1: + return CONSTRAINT_SPACE_LOCAL; + case 2: + return CONSTRAINT_SPACE_POSE; + case 3: + return CONSTRAINT_SPACE_PARLOCAL; + default: + return CONSTRAINT_SPACE_INVALID; + } + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java index 8c4c8593b..1317032b4 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java @@ -15,23 +15,24 @@ import com.jme3.scene.plugins.blender.file.Structure; * * @author Marcin Roguski (Kaelthas) */ -/*package*/ class SkeletonConstraint extends Constraint { - private static final Logger LOGGER = Logger.getLogger(SkeletonConstraint.class.getName()); - - public SkeletonConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { - super(constraintStructure, ownerOMA, influenceIpo, blenderContext); - } +/* package */class SkeletonConstraint extends Constraint { + private static final Logger LOGGER = Logger.getLogger(SkeletonConstraint.class.getName()); - @Override - public void performBakingOperation() { - LOGGER.warning("Applying constraints to skeleton is not supported."); - } + public SkeletonConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { + super(constraintStructure, ownerOMA, influenceIpo, blenderContext); + } - @Override - protected boolean validate() { - return true; - } - - @Override - protected void prepareTracksForApplyingConstraints() { } + @Override + public void performBakingOperation() { + LOGGER.warning("Applying constraints to skeleton is not supported."); + } + + @Override + protected boolean validate() { + return true; + } + + @Override + protected void prepareTracksForApplyingConstraints() { + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java index cc4ddff52..20ec82aa5 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java @@ -28,146 +28,144 @@ import com.jme3.scene.plugins.ogre.AnimData; * This includes: nodes, cameras nodes and light nodes. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class SpatialConstraint extends BoneConstraint { - private static final Logger LOGGER = Logger.getLogger(SpatialConstraint.class.getName()); - - /** The owner of the constraint. */ - private Spatial owner; - /** The target of the constraint. */ - private Object target; - - public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) - throws BlenderFileException { - super(constraintStructure, ownerOMA, influenceIpo, blenderContext); - } - - @Override - public void performBakingOperation() { - this.owner = (Spatial) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); - this.target = targetOMA != null ? blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) : null; - this.prepareTracksForApplyingConstraints(); - - //apply static constraint - Transform ownerTransform = constraintHelper.getNodeObjectTransform(ownerSpace, ownerOMA, blenderContext); - Transform targetTransform = targetOMA != null ? constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext) : null; - constraintDefinition.bake(ownerTransform, targetTransform, null, null, this.ipo); - constraintHelper.applyTransform(owner, ownerSpace, ownerTransform); - - //apply dynamic constraint - AnimData animData = blenderContext.getAnimData(ownerOMA); - if(animData != null) { - for(Animation animation : animData.anims) { - SpatialTrack ownerTrack = constraintHelper.getTrack(owner, animation); - - AnimData targetAnimData = blenderContext.getAnimData(targetOMA); - SpatialTrack targetTrack = null; - if(targetAnimData != null) { - targetTrack = constraintHelper.getTrack((Spatial)target, targetAnimData.anims.get(0)); - } - - constraintDefinition.bake(ownerTransform, targetTransform, ownerTrack, targetTrack, this.ipo); - } - } - } - - @Override - protected void prepareTracksForApplyingConstraints() { - Long[] spatialsOMAs = new Long[] { ownerOMA, targetOMA }; - Space[] spaces = new Space[] { ownerSpace, targetSpace }; - - //creating animations for current objects if at least on of their parents have an animation - for (int i = 0; i < spatialsOMAs.length; ++i) { - Long oma = spatialsOMAs[i]; - if(oma != null && oma > 0L) { - AnimData animData = blenderContext.getAnimData(oma); - if(animData == null) { - Spatial currentSpatial = (Spatial)blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE); - if(currentSpatial != null) { - if(currentSpatial.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) == Boolean.TRUE) {//look for it among bones - BoneContext currentBoneContext = blenderContext.getBoneByName(subtargetName); - Bone currentBone = currentBoneContext.getBone(); - Bone parent = currentBone.getParent(); - boolean foundAnimation = false; - while(parent != null && !foundAnimation) { - BoneContext boneContext = blenderContext.getBoneByName(parent.getName()); - foundAnimation = this.hasAnimation(boneContext.getBoneOma()); - animData = blenderContext.getAnimData(boneContext.getBoneOma()); - parent = parent.getParent(); - } - if(foundAnimation) { - this.applyAnimData(currentBoneContext, spaces[i], animData); - } - } else { - Spatial parent = currentSpatial.getParent(); - while(parent != null && animData == null) { - Structure parentStructure = (Structure)blenderContext.getLoadedFeature(parent.getName(), LoadedFeatureDataType.LOADED_STRUCTURE); - if(parentStructure == null) { - parent = null; - } else { - Long parentOma = parentStructure.getOldMemoryAddress(); - animData = blenderContext.getAnimData(parentOma); - parent = parent.getParent(); - } - } - - if(animData != null) {//create anim data for the current object - this.applyAnimData(currentSpatial, oma, spaces[i], animData.anims.get(0)); - } - } - } else { - LOGGER.warning("Couldn't find target object for constraint: " + name + - ". Make sure that the target is on layer that is defined to be loaded in blender key!"); - } - } - } - } - - //creating animation for owner if it doesn't have one already and if the target has it - AnimData animData = blenderContext.getAnimData(ownerOMA); - if(animData == null) { - AnimData targetAnimData = blenderContext.getAnimData(targetOMA); - if(targetAnimData != null) { - this.applyAnimData(owner, ownerOMA, ownerSpace, targetAnimData.anims.get(0)); - } - } - } - - /** - * This method applies spatial transform on each frame of the given - * animations. - * - * @param spatial - * the spatial - * @param spatialOma - * the OMA of the given spatial - * @param space - * the space we compute the transform in - * @param referenceAnimation - * the object containing the animations - */ - private void applyAnimData(Spatial spatial, Long spatialOma, Space space, Animation referenceAnimation) { - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - Transform transform = constraintHelper.getNodeObjectTransform(space, spatialOma, blenderContext); - - SpatialTrack parentTrack = (SpatialTrack) referenceAnimation.getTracks()[0]; - - HashMap anims = new HashMap(1); - Animation animation = new Animation(spatial.getName(), referenceAnimation.getLength()); - anims.put(spatial.getName(), animation); - - float[] times = parentTrack.getTimes(); - Vector3f[] translations = new Vector3f[times.length]; - Quaternion[] rotations = new Quaternion[times.length]; - Vector3f[] scales = new Vector3f[times.length]; - Arrays.fill(translations, transform.getTranslation()); - Arrays.fill(rotations, transform.getRotation()); - Arrays.fill(scales, transform.getScale()); - animation.addTrack(new SpatialTrack(times, translations, rotations, scales)); - - AnimControl control = new AnimControl(null); - control.setAnimations(anims); - spatial.addControl(control); - - blenderContext.setAnimData(spatialOma, new AnimData(null, new ArrayList(anims.values()))); - } +/* package */class SpatialConstraint extends BoneConstraint { + private static final Logger LOGGER = Logger.getLogger(SpatialConstraint.class.getName()); + + /** The owner of the constraint. */ + private Spatial owner; + /** The target of the constraint. */ + private Object target; + + public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { + super(constraintStructure, ownerOMA, influenceIpo, blenderContext); + } + + @Override + public void performBakingOperation() { + this.owner = (Spatial) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); + this.target = targetOMA != null ? blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) : null; + this.prepareTracksForApplyingConstraints(); + + // apply static constraint + Transform ownerTransform = constraintHelper.getNodeObjectTransform(ownerSpace, ownerOMA, blenderContext); + Transform targetTransform = targetOMA != null ? constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext) : null; + constraintDefinition.bake(ownerTransform, targetTransform, null, null, this.ipo); + constraintHelper.applyTransform(owner, ownerSpace, ownerTransform); + + // apply dynamic constraint + AnimData animData = blenderContext.getAnimData(ownerOMA); + if (animData != null) { + for (Animation animation : animData.anims) { + SpatialTrack ownerTrack = constraintHelper.getTrack(owner, animation); + + AnimData targetAnimData = blenderContext.getAnimData(targetOMA); + SpatialTrack targetTrack = null; + if (targetAnimData != null) { + targetTrack = constraintHelper.getTrack((Spatial) target, targetAnimData.anims.get(0)); + } + + constraintDefinition.bake(ownerTransform, targetTransform, ownerTrack, targetTrack, this.ipo); + } + } + } + + @Override + protected void prepareTracksForApplyingConstraints() { + Long[] spatialsOMAs = new Long[] { ownerOMA, targetOMA }; + Space[] spaces = new Space[] { ownerSpace, targetSpace }; + + // creating animations for current objects if at least on of their parents have an animation + for (int i = 0; i < spatialsOMAs.length; ++i) { + Long oma = spatialsOMAs[i]; + if (oma != null && oma > 0L) { + AnimData animData = blenderContext.getAnimData(oma); + if (animData == null) { + Spatial currentSpatial = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE); + if (currentSpatial != null) { + if (currentSpatial.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) == Boolean.TRUE) {// look for it among bones + BoneContext currentBoneContext = blenderContext.getBoneByName(subtargetName); + Bone currentBone = currentBoneContext.getBone(); + Bone parent = currentBone.getParent(); + boolean foundAnimation = false; + while (parent != null && !foundAnimation) { + BoneContext boneContext = blenderContext.getBoneByName(parent.getName()); + foundAnimation = this.hasAnimation(boneContext.getBoneOma()); + animData = blenderContext.getAnimData(boneContext.getBoneOma()); + parent = parent.getParent(); + } + if (foundAnimation) { + this.applyAnimData(currentBoneContext, spaces[i], animData); + } + } else { + Spatial parent = currentSpatial.getParent(); + while (parent != null && animData == null) { + Structure parentStructure = (Structure) blenderContext.getLoadedFeature(parent.getName(), LoadedFeatureDataType.LOADED_STRUCTURE); + if (parentStructure == null) { + parent = null; + } else { + Long parentOma = parentStructure.getOldMemoryAddress(); + animData = blenderContext.getAnimData(parentOma); + parent = parent.getParent(); + } + } + + if (animData != null) {// create anim data for the current object + this.applyAnimData(currentSpatial, oma, spaces[i], animData.anims.get(0)); + } + } + } else { + LOGGER.warning("Couldn't find target object for constraint: " + name + ". Make sure that the target is on layer that is defined to be loaded in blender key!"); + } + } + } + } + + // creating animation for owner if it doesn't have one already and if the target has it + AnimData animData = blenderContext.getAnimData(ownerOMA); + if (animData == null) { + AnimData targetAnimData = blenderContext.getAnimData(targetOMA); + if (targetAnimData != null) { + this.applyAnimData(owner, ownerOMA, ownerSpace, targetAnimData.anims.get(0)); + } + } + } + + /** + * This method applies spatial transform on each frame of the given + * animations. + * + * @param spatial + * the spatial + * @param spatialOma + * the OMA of the given spatial + * @param space + * the space we compute the transform in + * @param referenceAnimation + * the object containing the animations + */ + private void applyAnimData(Spatial spatial, Long spatialOma, Space space, Animation referenceAnimation) { + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + Transform transform = constraintHelper.getNodeObjectTransform(space, spatialOma, blenderContext); + + SpatialTrack parentTrack = (SpatialTrack) referenceAnimation.getTracks()[0]; + + HashMap anims = new HashMap(1); + Animation animation = new Animation(spatial.getName(), referenceAnimation.getLength()); + anims.put(spatial.getName(), animation); + + float[] times = parentTrack.getTimes(); + Vector3f[] translations = new Vector3f[times.length]; + Quaternion[] rotations = new Quaternion[times.length]; + Vector3f[] scales = new Vector3f[times.length]; + Arrays.fill(translations, transform.getTranslation()); + Arrays.fill(rotations, transform.getRotation()); + Arrays.fill(scales, transform.getScale()); + animation.addTrack(new SpatialTrack(times, translations, rotations, scales)); + + AnimControl control = new AnimControl(null); + control.setAnimations(anims); + spatial.addControl(control); + + blenderContext.setAnimData(spatialOma, new AnimData(null, new ArrayList(anims.values()))); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java index df5e0901b..91868db4d 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java @@ -19,230 +19,225 @@ import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.util.TempVars; public abstract class ConstraintDefinition { - protected int flag; - - public ConstraintDefinition(Structure constraintData, BlenderContext blenderContext) { - if(constraintData != null) {//Null constraint has no data - Number flag = (Number)constraintData.getFieldValue("flag"); - if(flag != null) { - this.flag = flag.intValue(); - } - } - } - - public void bake(Transform ownerTransform, Transform targetTransform, Track ownerTrack, Track targetTrack, Ipo influenceIpo) { - TrackWrapper ownerWrapperTrack = ownerTrack != null ? new TrackWrapper(ownerTrack) : null; - TrackWrapper targetWrapperTrack = targetTrack != null ? new TrackWrapper(targetTrack) : null; - - //uruchamiamy bake dla transformat zalenie od tego, ktre argumenty s nullami, a ktre - nie - this.bake(ownerTransform, targetTransform, influenceIpo.calculateValue(0)); - if(ownerWrapperTrack != null) { - float[] ownerTimes = ownerWrapperTrack.getTimes(); - Vector3f[] translations = ownerWrapperTrack.getTranslations(); - Quaternion[] rotations = ownerWrapperTrack.getRotations(); - Vector3f[] scales = ownerWrapperTrack.getScales(); - - float[] targetTimes = targetWrapperTrack == null ? null : targetWrapperTrack.getTimes(); - Vector3f[] targetTranslations = targetWrapperTrack == null ? null : targetWrapperTrack.getTranslations(); - Quaternion[] targetRotations = targetWrapperTrack == null ? null : targetWrapperTrack.getRotations(); - Vector3f[] targetScales = targetWrapperTrack == null ? null : targetWrapperTrack.getScales(); - Vector3f translation = new Vector3f(), scale = new Vector3f(); - Quaternion rotation = new Quaternion(); - - Transform ownerTemp = new Transform(), targetTemp = new Transform(); - for (int i = 0; i = targetTimes.length - 1) { - result.set(targetVectors[targetTimes.length - 1]); - } else { - float delta = targetTimes[index + 1] - targetTimes[index]; - if(delta == 0.0f) { - result.set(targetVectors[index + 1]); - } else { - float scale = (currentTime - targetTimes[index])/(targetTimes[index + 1] - targetTimes[index]); - FastMath.interpolateLinear(scale, targetVectors[index], targetVectors[index + 1], result); - } - } - } - - private void interpolate(Quaternion[] targetQuaternions, float[] targetTimes, float currentTime, Quaternion result) { - int index = 0; - for (int i = 1; i < targetTimes.length; ++i) { - if(targetTimes[i] < currentTime) { - ++index; - } else { - break; - } - } - if(index >= targetTimes.length - 1) { - result.set(targetQuaternions[targetTimes.length - 1]); - } else { - float delta = targetTimes[index + 1] - targetTimes[index]; - if(delta == 0.0f) { - result.set(targetQuaternions[index + 1]); - } else { - float scale = (currentTime - targetTimes[index])/(targetTimes[index + 1] - targetTimes[index]); - result.slerp(targetQuaternions[index], targetQuaternions[index + 1], scale); - } - } - } - - /** - * This class holds either the bone track or spatial track. Is made to improve - * code readability. - * - * @author Marcin Roguski (Kaelthas) - */ - private static class TrackWrapper implements Track { - /** The spatial track. */ - private SpatialTrack spatialTrack; - /** The bone track. */ - private BoneTrack boneTrack; - - /** - * Constructs the object using the given track. The track must be of one of the types: - *
  • BoneTrack - *
  • SpatialTrack - * - * @param track - * the animation track - */ - public TrackWrapper(Track track) { - if(track instanceof SpatialTrack) { - this.spatialTrack = (SpatialTrack)track; - } else if(track instanceof BoneTrack) { - this.boneTrack = (BoneTrack)track; - } else { - throw new IllegalStateException("Unknown track type!"); - } - } - - /** - * @return the array of rotations of this track - */ - public Quaternion[] getRotations() { - if (boneTrack != null) { - return boneTrack.getRotations(); - } - return spatialTrack.getRotations(); - } - - /** - * @return the array of scales for this track - */ - public Vector3f[] getScales() { - if (boneTrack != null) { - return boneTrack.getScales(); - } - return spatialTrack.getScales(); - } - - /** - * @return the arrays of time for this track - */ - public float[] getTimes() { - if (boneTrack != null) { - return boneTrack.getTimes(); - } - return spatialTrack.getTimes(); - } - - /** - * @return the array of translations of this track - */ - public Vector3f[] getTranslations() { - if (boneTrack != null) { - return boneTrack.getTranslations(); - } - return spatialTrack.getTranslations(); - } - - /** - * Set the translations, rotations and scales for this bone track - * - * @param times - * a float array with the time of each frame - * @param translations - * the translation of the bone for each frame - * @param rotations - * the rotation of the bone for each frame - * @param scales - * the scale of the bone for each frame - */ - public void setKeyframes(float[] times, Vector3f[] translations, - Quaternion[] rotations, Vector3f[] scales) { - if (boneTrack != null) { - boneTrack.setKeyframes(times, translations, rotations, scales); - } else { - spatialTrack.setKeyframes(times, translations, rotations, scales); - } - } - - public void write(JmeExporter ex) throws IOException { - //no need to implement this one (the TrackWrapper is used internally and never serialized) - } - - public void read(JmeImporter im) throws IOException { - //no need to implement this one (the TrackWrapper is used internally and never serialized) - } - - public void setTime(float time, float weight, AnimControl control, - AnimChannel channel, TempVars vars) { - if (boneTrack != null) { - boneTrack.setTime(time, weight, control, channel, vars); - } else { - spatialTrack.setTime(time, weight, control, channel, vars); - } - } - - public float getLength() { - return spatialTrack == null ? boneTrack.getLength() : spatialTrack - .getLength(); - } - - @Override - public TrackWrapper clone() { - if (boneTrack != null) { - return new TrackWrapper(boneTrack.clone()); - } - return new TrackWrapper(spatialTrack.clone()); - } - } + protected int flag; + + public ConstraintDefinition(Structure constraintData, BlenderContext blenderContext) { + if (constraintData != null) {// Null constraint has no data + Number flag = (Number) constraintData.getFieldValue("flag"); + if (flag != null) { + this.flag = flag.intValue(); + } + } + } + + public void bake(Transform ownerTransform, Transform targetTransform, Track ownerTrack, Track targetTrack, Ipo influenceIpo) { + TrackWrapper ownerWrapperTrack = ownerTrack != null ? new TrackWrapper(ownerTrack) : null; + TrackWrapper targetWrapperTrack = targetTrack != null ? new TrackWrapper(targetTrack) : null; + + // uruchamiamy bake dla transformat zalenie od tego, ktre argumenty s nullami, a ktre - nie + this.bake(ownerTransform, targetTransform, influenceIpo.calculateValue(0)); + if (ownerWrapperTrack != null) { + float[] ownerTimes = ownerWrapperTrack.getTimes(); + Vector3f[] translations = ownerWrapperTrack.getTranslations(); + Quaternion[] rotations = ownerWrapperTrack.getRotations(); + Vector3f[] scales = ownerWrapperTrack.getScales(); + + float[] targetTimes = targetWrapperTrack == null ? null : targetWrapperTrack.getTimes(); + Vector3f[] targetTranslations = targetWrapperTrack == null ? null : targetWrapperTrack.getTranslations(); + Quaternion[] targetRotations = targetWrapperTrack == null ? null : targetWrapperTrack.getRotations(); + Vector3f[] targetScales = targetWrapperTrack == null ? null : targetWrapperTrack.getScales(); + Vector3f translation = new Vector3f(), scale = new Vector3f(); + Quaternion rotation = new Quaternion(); + + Transform ownerTemp = new Transform(), targetTemp = new Transform(); + for (int i = 0; i < ownerTimes.length; ++i) { + float t = ownerTimes[i]; + ownerTemp.setTranslation(translations[i]); + ownerTemp.setRotation(rotations[i]); + ownerTemp.setScale(scales[i]); + if (targetWrapperTrack == null) { + this.bake(ownerTemp, targetTransform, influenceIpo.calculateValue(i)); + } else { + // getting the values that are the interpolation of the target track for the time 't' + this.interpolate(targetTranslations, targetTimes, t, translation); + this.interpolate(targetRotations, targetTimes, t, rotation); + this.interpolate(targetScales, targetTimes, t, scale); + + targetTemp.setTranslation(translation); + targetTemp.setRotation(rotation); + targetTemp.setScale(scale); + + this.bake(ownerTemp, targetTemp, influenceIpo.calculateValue(i)); + } + // need to clone here because each of the arrays will reference the same instance if they hold the same value in the compact array + translations[i] = ownerTemp.getTranslation().clone(); + rotations[i] = ownerTemp.getRotation().clone(); + scales[i] = ownerTemp.getScale().clone(); + } + ownerWrapperTrack.setKeyframes(ownerTimes, translations, rotations, scales); + } + } + + protected abstract void bake(Transform ownerTransform, Transform targetTransform, float influence); + + private void interpolate(Vector3f[] targetVectors, float[] targetTimes, float currentTime, Vector3f result) { + int index = 0; + for (int i = 1; i < targetTimes.length; ++i) { + if (targetTimes[i] < currentTime) { + ++index; + } else { + break; + } + } + if (index >= targetTimes.length - 1) { + result.set(targetVectors[targetTimes.length - 1]); + } else { + float delta = targetTimes[index + 1] - targetTimes[index]; + if (delta == 0.0f) { + result.set(targetVectors[index + 1]); + } else { + float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]); + FastMath.interpolateLinear(scale, targetVectors[index], targetVectors[index + 1], result); + } + } + } + + private void interpolate(Quaternion[] targetQuaternions, float[] targetTimes, float currentTime, Quaternion result) { + int index = 0; + for (int i = 1; i < targetTimes.length; ++i) { + if (targetTimes[i] < currentTime) { + ++index; + } else { + break; + } + } + if (index >= targetTimes.length - 1) { + result.set(targetQuaternions[targetTimes.length - 1]); + } else { + float delta = targetTimes[index + 1] - targetTimes[index]; + if (delta == 0.0f) { + result.set(targetQuaternions[index + 1]); + } else { + float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]); + result.slerp(targetQuaternions[index], targetQuaternions[index + 1], scale); + } + } + } + + /** + * This class holds either the bone track or spatial track. Is made to improve + * code readability. + * + * @author Marcin Roguski (Kaelthas) + */ + private static class TrackWrapper implements Track { + /** The spatial track. */ + private SpatialTrack spatialTrack; + /** The bone track. */ + private BoneTrack boneTrack; + + /** + * Constructs the object using the given track. The track must be of one of the types:
  • BoneTrack
  • SpatialTrack + * + * @param track + * the animation track + */ + public TrackWrapper(Track track) { + if (track instanceof SpatialTrack) { + this.spatialTrack = (SpatialTrack) track; + } else if (track instanceof BoneTrack) { + this.boneTrack = (BoneTrack) track; + } else { + throw new IllegalStateException("Unknown track type!"); + } + } + + /** + * @return the array of rotations of this track + */ + public Quaternion[] getRotations() { + if (boneTrack != null) { + return boneTrack.getRotations(); + } + return spatialTrack.getRotations(); + } + + /** + * @return the array of scales for this track + */ + public Vector3f[] getScales() { + if (boneTrack != null) { + return boneTrack.getScales(); + } + return spatialTrack.getScales(); + } + + /** + * @return the arrays of time for this track + */ + public float[] getTimes() { + if (boneTrack != null) { + return boneTrack.getTimes(); + } + return spatialTrack.getTimes(); + } + + /** + * @return the array of translations of this track + */ + public Vector3f[] getTranslations() { + if (boneTrack != null) { + return boneTrack.getTranslations(); + } + return spatialTrack.getTranslations(); + } + + /** + * Set the translations, rotations and scales for this bone track + * + * @param times + * a float array with the time of each frame + * @param translations + * the translation of the bone for each frame + * @param rotations + * the rotation of the bone for each frame + * @param scales + * the scale of the bone for each frame + */ + public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) { + if (boneTrack != null) { + boneTrack.setKeyframes(times, translations, rotations, scales); + } else { + spatialTrack.setKeyframes(times, translations, rotations, scales); + } + } + + public void write(JmeExporter ex) throws IOException { + // no need to implement this one (the TrackWrapper is used internally and never serialized) + } + + public void read(JmeImporter im) throws IOException { + // no need to implement this one (the TrackWrapper is used internally and never serialized) + } + + public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { + if (boneTrack != null) { + boneTrack.setTime(time, weight, control, channel, vars); + } else { + spatialTrack.setTime(time, weight, control, channel, vars); + } + } + + public float getLength() { + return spatialTrack == null ? boneTrack.getLength() : spatialTrack.getLength(); + } + + @Override + public TrackWrapper clone() { + if (boneTrack != null) { + return new TrackWrapper(boneTrack.clone()); + } + return new TrackWrapper(spatialTrack.clone()); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionAction.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionAction.java index 77935ee2f..63d04e073 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionAction.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionAction.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Action' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionAction extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); - - public ConstraintDefinitionAction(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } +/* package */class ConstraintDefinitionAction extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Action' constraint - LOGGER.log(Level.WARNING, "'Action' constraint NOT implemented!"); - } + public ConstraintDefinitionAction(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Action' constraint + LOGGER.log(Level.WARNING, "'Action' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionCameraSolver.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionCameraSolver.java index ba327183b..5f51d02ad 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionCameraSolver.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionCameraSolver.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Camera solver' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionCameraSolver extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); - - public ConstraintDefinitionCameraSolver(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } +/* package */class ConstraintDefinitionCameraSolver extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Camera solver' constraint - LOGGER.log(Level.WARNING, "'Camera solver' constraint NOT implemented!"); - } + public ConstraintDefinitionCameraSolver(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Camera solver' constraint + LOGGER.log(Level.WARNING, "'Camera solver' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionChildOf.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionChildOf.java index 8c1c2c300..0e27c7cfa 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionChildOf.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionChildOf.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'ChildOf' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionChildOf extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionChildOf.class.getName()); - - public ConstraintDefinitionChildOf(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement ChildOf constraint - LOGGER.log(Level.WARNING, "ChildOf constraint NOT implemented!"); - } +/* package */class ConstraintDefinitionChildOf extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionChildOf.class.getName()); + + public ConstraintDefinitionChildOf(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement ChildOf constraint + LOGGER.log(Level.WARNING, "ChildOf constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionClampTo.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionClampTo.java index 08de7fadc..aa0161b93 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionClampTo.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionClampTo.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Clamp to' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionClampTo extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionClampTo.class.getName()); - - public ConstraintDefinitionClampTo(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - //TODO: implement when curves are implemented - LOGGER.log(Level.WARNING, "'Clamp to' not yet implemented!"); - } +/* package */class ConstraintDefinitionClampTo extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionClampTo.class.getName()); + + public ConstraintDefinitionClampTo(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement when curves are implemented + LOGGER.log(Level.WARNING, "'Clamp to' not yet implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDampTrack.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDampTrack.java index dd9ee8c2c..9ebdf1ee4 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDampTrack.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDampTrack.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * The damp track constraint. Available for blender 2.50+. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionDampTrack extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionDampTrack.class.getName()); - - public ConstraintDefinitionDampTrack(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO Auto-generated method stub - LOGGER.log(Level.WARNING, "'Damp Track' constraint NOT implemented!"); - } +/* package */class ConstraintDefinitionDampTrack extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionDampTrack.class.getName()); + + public ConstraintDefinitionDampTrack(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO Auto-generated method stub + LOGGER.log(Level.WARNING, "'Damp Track' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java index fdaa7eeeb..18231ed23 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java @@ -9,51 +9,51 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Dist limit' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionDistLimit extends ConstraintDefinition { - private static final int LIMITDIST_INSIDE = 0; - private static final int LIMITDIST_OUTSIDE = 1; - private static final int LIMITDIST_ONSURFACE = 2; - - protected int mode; - protected float dist; - - public ConstraintDefinitionDistLimit(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - mode = ((Number) constraintData.getFieldValue("mode")).intValue(); - dist = ((Number) constraintData.getFieldValue("dist")).floatValue(); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation()); - float currentDistance = v.length(); - - switch (mode) { - case LIMITDIST_INSIDE: - if (currentDistance >= dist) { - v.normalizeLocal(); - v.multLocal(dist + (currentDistance - dist) * (1.0f - influence)); - ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation())); - } - break; - case LIMITDIST_ONSURFACE: - if (currentDistance > dist) { - v.normalizeLocal(); - v.multLocal(dist + (currentDistance - dist) * (1.0f - influence)); - ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation())); - } else if(currentDistance < dist) { - v.normalizeLocal().multLocal(dist * influence); - ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v)); - } - break; - case LIMITDIST_OUTSIDE: - if (currentDistance <= dist) { - v = targetTransform.getTranslation().subtract(ownerTransform.getTranslation()).normalizeLocal().multLocal(dist * influence); - ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v)); - } - break; - default: - throw new IllegalStateException("Unknown distance limit constraint mode: " + mode); - } - } +/* package */class ConstraintDefinitionDistLimit extends ConstraintDefinition { + private static final int LIMITDIST_INSIDE = 0; + private static final int LIMITDIST_OUTSIDE = 1; + private static final int LIMITDIST_ONSURFACE = 2; + + protected int mode; + protected float dist; + + public ConstraintDefinitionDistLimit(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + mode = ((Number) constraintData.getFieldValue("mode")).intValue(); + dist = ((Number) constraintData.getFieldValue("dist")).floatValue(); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation()); + float currentDistance = v.length(); + + switch (mode) { + case LIMITDIST_INSIDE: + if (currentDistance >= dist) { + v.normalizeLocal(); + v.multLocal(dist + (currentDistance - dist) * (1.0f - influence)); + ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation())); + } + break; + case LIMITDIST_ONSURFACE: + if (currentDistance > dist) { + v.normalizeLocal(); + v.multLocal(dist + (currentDistance - dist) * (1.0f - influence)); + ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation())); + } else if (currentDistance < dist) { + v.normalizeLocal().multLocal(dist * influence); + ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v)); + } + break; + case LIMITDIST_OUTSIDE: + if (currentDistance <= dist) { + v = targetTransform.getTranslation().subtract(ownerTransform.getTranslation()).normalizeLocal().multLocal(dist * influence); + ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v)); + } + break; + default: + throw new IllegalStateException("Unknown distance limit constraint mode: " + mode); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java index 603bb6bfe..be82d2ed7 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java @@ -40,75 +40,75 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.file.Structure; public class ConstraintDefinitionFactory { - private static final Map> CONSTRAINT_CLASSES = new HashMap>(); - static { - CONSTRAINT_CLASSES.put("bActionConstraint", ConstraintDefinitionAction.class); - CONSTRAINT_CLASSES.put("bChildOfConstraint", ConstraintDefinitionChildOf.class); - CONSTRAINT_CLASSES.put("bClampToConstraint", ConstraintDefinitionClampTo.class); - CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class); - CONSTRAINT_CLASSES.put("bFollowPathConstraint", ConstraintDefinitionFollowPath.class); - CONSTRAINT_CLASSES.put("bKinematicConstraint", ConstraintDefinitionInverseKinematics.class); - CONSTRAINT_CLASSES.put("bLockTrackConstraint", ConstraintDefinitionLockTrack.class); - CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class); - CONSTRAINT_CLASSES.put("bLocLimitConstraint", ConstraintDefinitionLocLimit.class); - CONSTRAINT_CLASSES.put("bMinMaxConstraint", ConstraintDefinitionMinMax.class); - CONSTRAINT_CLASSES.put("bNullConstraint", ConstraintDefinitionNull.class); - CONSTRAINT_CLASSES.put("bPythonConstraint", ConstraintDefinitionPython.class); - CONSTRAINT_CLASSES.put("bRigidBodyJointConstraint", ConstraintDefinitionRigidBodyJoint.class); - CONSTRAINT_CLASSES.put("bRotateLikeConstraint", ConstraintDefinitionRotLike.class); - CONSTRAINT_CLASSES.put("bShrinkWrapConstraint", ConstraintDefinitionShrinkWrap.class); - CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class); - CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class); - CONSTRAINT_CLASSES.put("bStretchToConstraint", ConstraintDefinitionStretchTo.class); - CONSTRAINT_CLASSES.put("bTransformConstraint", ConstraintDefinitionTransform.class); - CONSTRAINT_CLASSES.put("bRotLimitConstraint", ConstraintDefinitionRotLimit.class); - //Blender 2.50+ - CONSTRAINT_CLASSES.put("bSplineIKConstraint", ConstraintDefinitionSplineInverseKinematic.class); - CONSTRAINT_CLASSES.put("bDampTrackConstraint", ConstraintDefinitionDampTrack.class); - CONSTRAINT_CLASSES.put("bPivotConstraint", ConstraintDefinitionDampTrack.class); - //Blender 2.56+ - CONSTRAINT_CLASSES.put("bTrackToConstraint", ConstraintDefinitionTrackTo.class); - CONSTRAINT_CLASSES.put("bSameVolumeConstraint", ConstraintDefinitionSameVolume.class); - CONSTRAINT_CLASSES.put("bTransLikeConstraint", ConstraintDefinitionTransLike.class); - //Blender 2.62+ - CONSTRAINT_CLASSES.put("bCameraSolverConstraint", ConstraintDefinitionCameraSolver.class); - CONSTRAINT_CLASSES.put("bObjectSolverConstraint", ConstraintDefinitionObjectSolver.class); - CONSTRAINT_CLASSES.put("bFollowTrackConstraint", ConstraintDefinitionFollowTrack.class); - } - - /** - * This method creates the constraint instance. - * - * @param constraintStructure - * the constraint's structure (bConstraint clss in blender 2.49). If the value is null the NullConstraint is created. - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, BlenderContext blenderContext) throws BlenderFileException { - if(constraintStructure == null) { - return new ConstraintDefinitionNull(null, blenderContext); - } - String constraintClassName = constraintStructure.getType(); - Class constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName); - if(constraintDefinitionClass != null) { - try { - return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, blenderContext); - } catch (IllegalArgumentException e) { - throw new BlenderFileException(e.getLocalizedMessage(), e); - } catch (SecurityException e) { - throw new BlenderFileException(e.getLocalizedMessage(), e); - } catch (InstantiationException e) { - throw new BlenderFileException(e.getLocalizedMessage(), e); - } catch (IllegalAccessException e) { - throw new BlenderFileException(e.getLocalizedMessage(), e); - } catch (InvocationTargetException e) { - throw new BlenderFileException(e.getLocalizedMessage(), e); - } - } else { - throw new BlenderFileException("Unknown constraint type: " + constraintClassName); - } - } + private static final Map> CONSTRAINT_CLASSES = new HashMap>(); + static { + CONSTRAINT_CLASSES.put("bActionConstraint", ConstraintDefinitionAction.class); + CONSTRAINT_CLASSES.put("bChildOfConstraint", ConstraintDefinitionChildOf.class); + CONSTRAINT_CLASSES.put("bClampToConstraint", ConstraintDefinitionClampTo.class); + CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class); + CONSTRAINT_CLASSES.put("bFollowPathConstraint", ConstraintDefinitionFollowPath.class); + CONSTRAINT_CLASSES.put("bKinematicConstraint", ConstraintDefinitionInverseKinematics.class); + CONSTRAINT_CLASSES.put("bLockTrackConstraint", ConstraintDefinitionLockTrack.class); + CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class); + CONSTRAINT_CLASSES.put("bLocLimitConstraint", ConstraintDefinitionLocLimit.class); + CONSTRAINT_CLASSES.put("bMinMaxConstraint", ConstraintDefinitionMinMax.class); + CONSTRAINT_CLASSES.put("bNullConstraint", ConstraintDefinitionNull.class); + CONSTRAINT_CLASSES.put("bPythonConstraint", ConstraintDefinitionPython.class); + CONSTRAINT_CLASSES.put("bRigidBodyJointConstraint", ConstraintDefinitionRigidBodyJoint.class); + CONSTRAINT_CLASSES.put("bRotateLikeConstraint", ConstraintDefinitionRotLike.class); + CONSTRAINT_CLASSES.put("bShrinkWrapConstraint", ConstraintDefinitionShrinkWrap.class); + CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class); + CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class); + CONSTRAINT_CLASSES.put("bStretchToConstraint", ConstraintDefinitionStretchTo.class); + CONSTRAINT_CLASSES.put("bTransformConstraint", ConstraintDefinitionTransform.class); + CONSTRAINT_CLASSES.put("bRotLimitConstraint", ConstraintDefinitionRotLimit.class); + // Blender 2.50+ + CONSTRAINT_CLASSES.put("bSplineIKConstraint", ConstraintDefinitionSplineInverseKinematic.class); + CONSTRAINT_CLASSES.put("bDampTrackConstraint", ConstraintDefinitionDampTrack.class); + CONSTRAINT_CLASSES.put("bPivotConstraint", ConstraintDefinitionDampTrack.class); + // Blender 2.56+ + CONSTRAINT_CLASSES.put("bTrackToConstraint", ConstraintDefinitionTrackTo.class); + CONSTRAINT_CLASSES.put("bSameVolumeConstraint", ConstraintDefinitionSameVolume.class); + CONSTRAINT_CLASSES.put("bTransLikeConstraint", ConstraintDefinitionTransLike.class); + // Blender 2.62+ + CONSTRAINT_CLASSES.put("bCameraSolverConstraint", ConstraintDefinitionCameraSolver.class); + CONSTRAINT_CLASSES.put("bObjectSolverConstraint", ConstraintDefinitionObjectSolver.class); + CONSTRAINT_CLASSES.put("bFollowTrackConstraint", ConstraintDefinitionFollowTrack.class); + } + + /** + * This method creates the constraint instance. + * + * @param constraintStructure + * the constraint's structure (bConstraint clss in blender 2.49). If the value is null the NullConstraint is created. + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, BlenderContext blenderContext) throws BlenderFileException { + if (constraintStructure == null) { + return new ConstraintDefinitionNull(null, blenderContext); + } + String constraintClassName = constraintStructure.getType(); + Class constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName); + if (constraintDefinitionClass != null) { + try { + return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, blenderContext); + } catch (IllegalArgumentException e) { + throw new BlenderFileException(e.getLocalizedMessage(), e); + } catch (SecurityException e) { + throw new BlenderFileException(e.getLocalizedMessage(), e); + } catch (InstantiationException e) { + throw new BlenderFileException(e.getLocalizedMessage(), e); + } catch (IllegalAccessException e) { + throw new BlenderFileException(e.getLocalizedMessage(), e); + } catch (InvocationTargetException e) { + throw new BlenderFileException(e.getLocalizedMessage(), e); + } + } else { + throw new BlenderFileException("Unknown constraint type: " + constraintClassName); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFollowPath.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFollowPath.java index 0a94d031b..7103fe9b8 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFollowPath.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFollowPath.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Follow path' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionFollowPath extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionFollowPath.class.getName()); - - public ConstraintDefinitionFollowPath(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - //TODO: implement when curves are implemented - LOGGER.log(Level.WARNING, "'Follow path' not implemented! Curves not yet implemented!"); - } +/* package */class ConstraintDefinitionFollowPath extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionFollowPath.class.getName()); + + public ConstraintDefinitionFollowPath(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement when curves are implemented + LOGGER.log(Level.WARNING, "'Follow path' not implemented! Curves not yet implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFollowTrack.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFollowTrack.java index 458464d59..483d26c47 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFollowTrack.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFollowTrack.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Follow track' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionFollowTrack extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); - - public ConstraintDefinitionFollowTrack(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } +/* package */class ConstraintDefinitionFollowTrack extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Follow track' constraint - LOGGER.log(Level.WARNING, "'Follow track' constraint NOT implemented!"); - } + public ConstraintDefinitionFollowTrack(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Follow track' constraint + LOGGER.log(Level.WARNING, "'Follow track' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionInverseKinematics.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionInverseKinematics.java index e8d035290..24134b220 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionInverseKinematics.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionInverseKinematics.java @@ -11,134 +11,134 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Inverse kinematics' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionInverseKinematics extends ConstraintDefinition { - //private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionInverseKinematics.class.getName()); - //private static final float IK_SOLVER_ERROR = 0.5f; - - public ConstraintDefinitionInverseKinematics(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } +/* package */class ConstraintDefinitionInverseKinematics extends ConstraintDefinition { + // private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionInverseKinematics.class.getName()); + // private static final float IK_SOLVER_ERROR = 0.5f; - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { -// try { - // IK solver is only attached to bones -// Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); -// AnimData animData = blenderContext.getAnimData(ownerOMA); -// if(animData == null) { - //TODO: to nie moxe byx null, utworzyx dane bez ruchu, w zalexnoxci czy target six rusza -// } - - //prepare a list of all parents of this bone -// CalculationBone[] bones = this.getBonesToCalculate(skeleton, boneAnimation); - - // get the target point -// Object targetObject = this.getTarget(LoadedFeatureDataType.LOADED_FEATURE); -// Vector3f pt = null;// Point Target -// if (targetObject instanceof Bone) { -// pt = ((Bone) targetObject).getModelSpacePosition(); -// } else if (targetObject instanceof Spatial) { -// pt = ((Spatial) targetObject).getWorldTranslation(); -// } else if (targetObject instanceof Skeleton) { -// Structure armatureNodeStructure = (Structure) this.getTarget(LoadedFeatureDataType.LOADED_STRUCTURE); -// ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); -// Transform transform = objectHelper.getTransformation(armatureNodeStructure, blenderContext); -// pt = transform.getTranslation(); -// } else { -// throw new IllegalStateException( -// "Unknown target object type! Should be Node, Bone or Skeleton and there is: " -// + targetObject.getClass().getName()); -// } - - //fetching the owner's bone track -// BoneTrack ownerBoneTrack = null; -// int boneIndex = skeleton.getBoneIndex(ownerBone); -// for (int i = 0; i < boneAnimation.getTracks().length; ++i) { -// if (boneAnimation.getTracks()[i].getTargetBoneIndex() == boneIndex) { -// ownerBoneTrack = boneAnimation.getTracks()[i]; -// break; -// } -// } -// int ownerBoneFramesCount = ownerBoneTrack==null ? 0 : ownerBoneTrack.getTimes().length; -// -// // preparing data -// int maxIterations = ((Number) data.getFieldValue("iterations")).intValue(); -// CalculationBone[] bones = this.getBonesToCalculate(ownerBone, skeleton, boneAnimation); -// for (int i = 0; i < bones.length; ++i) { -// System.out.println(Arrays.toString(bones[i].track.getTranslations())); -// System.out.println(Arrays.toString(bones[i].track.getRotations())); -// System.out.println("==============================="); -// } -// Quaternion rotation = new Quaternion(); -// //all tracks should have the same amount of frames -// int framesCount = bones[0].getBoneFramesCount(); -// assert framesCount >=1; -// for (int frame = 0; frame < framesCount; ++frame) { -// float error = IK_SOLVER_ERROR; -// int iteration = 0; -// while (error >= IK_SOLVER_ERROR && iteration <= maxIterations) { -// // rotating the bones -// for (int i = 0; i < bones.length - 1; ++i) { -// Vector3f pe = bones[i].getEndPoint(); -// Vector3f pc = bones[i + 1].getWorldTranslation().clone(); -// -// Vector3f peSUBpc = pe.subtract(pc).normalizeLocal(); -// Vector3f ptSUBpc = pt.subtract(pc).normalizeLocal(); -// -// float theta = FastMath.acos(peSUBpc.dot(ptSUBpc)); -// Vector3f direction = peSUBpc.cross(ptSUBpc).normalizeLocal(); -// bones[i].rotate(rotation.fromAngleAxis(theta, direction), frame); -// } -// error = pt.subtract(bones[0].getEndPoint()).length(); -// ++iteration; -// } -// } -// -// for (CalculationBone bone : bones) { -// bone.applyCalculatedTracks(); -// } -// -// System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); -// for (int i = 0; i < bones.length; ++i) { -// System.out.println(Arrays.toString(bones[i].track.getTranslations())); -// System.out.println(Arrays.toString(bones[i].track.getRotations())); -// System.out.println("==============================="); -// } -// } catch(BlenderFileException e) { -// LOGGER.severe(e.getLocalizedMessage()); -// } - } - - /** - * This method returns bones used for rotation calculations. - * @param bone - * the bone to which the constraint is applied - * @param skeleton - * the skeleton owning the bone and its ancestors - * @param boneAnimation - * the bone animation data that stores the traces for the skeleton's bones - * @return a list of bones to imitate the bone's movement during IK solving - */ - private CalculationBone[] getBonesToCalculate(Skeleton skeleton, Animation boneAnimation) { -// Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); -// List bonesList = new ArrayList(); -// do { -// bonesList.add(new CalculationBone(ownerBone, 1)); -// int boneIndex = skeleton.getBoneIndex(ownerBone); -// for (int i = 0; i < boneAnimation.getTracks().length; ++i) { -// if (((BoneTrack[])boneAnimation.getTracks())[i].getTargetBoneIndex() == boneIndex) { -// bonesList.add(new CalculationBone(ownerBone, (BoneTrack)boneAnimation.getTracks()[i])); -// break; -// } -// } -// ownerBone = ownerBone.getParent(); -// } while (ownerBone != null); -// //attaching children -// CalculationBone[] result = bonesList.toArray(new CalculationBone[bonesList.size()]); -// for (int i = result.length - 1; i > 0; --i) { -// result[i].attachChild(result[i - 1]); -// } -// return result; - return null; - } + public ConstraintDefinitionInverseKinematics(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // try { + // IK solver is only attached to bones + // Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); + // AnimData animData = blenderContext.getAnimData(ownerOMA); + // if(animData == null) { + // TODO: to nie moxe byx null, utworzyx dane bez ruchu, w zalexnoxci czy target six rusza + // } + + // prepare a list of all parents of this bone + // CalculationBone[] bones = this.getBonesToCalculate(skeleton, boneAnimation); + + // get the target point + // Object targetObject = this.getTarget(LoadedFeatureDataType.LOADED_FEATURE); + // Vector3f pt = null;// Point Target + // if (targetObject instanceof Bone) { + // pt = ((Bone) targetObject).getModelSpacePosition(); + // } else if (targetObject instanceof Spatial) { + // pt = ((Spatial) targetObject).getWorldTranslation(); + // } else if (targetObject instanceof Skeleton) { + // Structure armatureNodeStructure = (Structure) this.getTarget(LoadedFeatureDataType.LOADED_STRUCTURE); + // ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + // Transform transform = objectHelper.getTransformation(armatureNodeStructure, blenderContext); + // pt = transform.getTranslation(); + // } else { + // throw new IllegalStateException( + // "Unknown target object type! Should be Node, Bone or Skeleton and there is: " + // + targetObject.getClass().getName()); + // } + + // fetching the owner's bone track + // BoneTrack ownerBoneTrack = null; + // int boneIndex = skeleton.getBoneIndex(ownerBone); + // for (int i = 0; i < boneAnimation.getTracks().length; ++i) { + // if (boneAnimation.getTracks()[i].getTargetBoneIndex() == boneIndex) { + // ownerBoneTrack = boneAnimation.getTracks()[i]; + // break; + // } + // } + // int ownerBoneFramesCount = ownerBoneTrack==null ? 0 : ownerBoneTrack.getTimes().length; + // + // // preparing data + // int maxIterations = ((Number) data.getFieldValue("iterations")).intValue(); + // CalculationBone[] bones = this.getBonesToCalculate(ownerBone, skeleton, boneAnimation); + // for (int i = 0; i < bones.length; ++i) { + // System.out.println(Arrays.toString(bones[i].track.getTranslations())); + // System.out.println(Arrays.toString(bones[i].track.getRotations())); + // System.out.println("==============================="); + // } + // Quaternion rotation = new Quaternion(); + // //all tracks should have the same amount of frames + // int framesCount = bones[0].getBoneFramesCount(); + // assert framesCount >=1; + // for (int frame = 0; frame < framesCount; ++frame) { + // float error = IK_SOLVER_ERROR; + // int iteration = 0; + // while (error >= IK_SOLVER_ERROR && iteration <= maxIterations) { + // // rotating the bones + // for (int i = 0; i < bones.length - 1; ++i) { + // Vector3f pe = bones[i].getEndPoint(); + // Vector3f pc = bones[i + 1].getWorldTranslation().clone(); + // + // Vector3f peSUBpc = pe.subtract(pc).normalizeLocal(); + // Vector3f ptSUBpc = pt.subtract(pc).normalizeLocal(); + // + // float theta = FastMath.acos(peSUBpc.dot(ptSUBpc)); + // Vector3f direction = peSUBpc.cross(ptSUBpc).normalizeLocal(); + // bones[i].rotate(rotation.fromAngleAxis(theta, direction), frame); + // } + // error = pt.subtract(bones[0].getEndPoint()).length(); + // ++iteration; + // } + // } + // + // for (CalculationBone bone : bones) { + // bone.applyCalculatedTracks(); + // } + // + // System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); + // for (int i = 0; i < bones.length; ++i) { + // System.out.println(Arrays.toString(bones[i].track.getTranslations())); + // System.out.println(Arrays.toString(bones[i].track.getRotations())); + // System.out.println("==============================="); + // } + // } catch(BlenderFileException e) { + // LOGGER.severe(e.getLocalizedMessage()); + // } + } + + /** + * This method returns bones used for rotation calculations. + * @param bone + * the bone to which the constraint is applied + * @param skeleton + * the skeleton owning the bone and its ancestors + * @param boneAnimation + * the bone animation data that stores the traces for the skeleton's bones + * @return a list of bones to imitate the bone's movement during IK solving + */ + private CalculationBone[] getBonesToCalculate(Skeleton skeleton, Animation boneAnimation) { + // Bone ownerBone = (Bone) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); + // List bonesList = new ArrayList(); + // do { + // bonesList.add(new CalculationBone(ownerBone, 1)); + // int boneIndex = skeleton.getBoneIndex(ownerBone); + // for (int i = 0; i < boneAnimation.getTracks().length; ++i) { + // if (((BoneTrack[])boneAnimation.getTracks())[i].getTargetBoneIndex() == boneIndex) { + // bonesList.add(new CalculationBone(ownerBone, (BoneTrack)boneAnimation.getTracks()[i])); + // break; + // } + // } + // ownerBone = ownerBone.getParent(); + // } while (ownerBone != null); + // //attaching children + // CalculationBone[] result = bonesList.toArray(new CalculationBone[bonesList.size()]); + // for (int i = result.length - 1; i > 0; --i) { + // result[i].attachChild(result[i - 1]); + // } + // return result; + return null; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java index 129da2a65..a9bbd89d9 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java @@ -9,66 +9,66 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Loc like' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionLocLike extends ConstraintDefinition { - private static final int LOCLIKE_X = 0x01; - private static final int LOCLIKE_Y = 0x02; - private static final int LOCLIKE_Z = 0x04; - //protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in blender +/* package */class ConstraintDefinitionLocLike extends ConstraintDefinition { + private static final int LOCLIKE_X = 0x01; + private static final int LOCLIKE_Y = 0x02; + private static final int LOCLIKE_Z = 0x04; + // protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in blender private static final int LOCLIKE_X_INVERT = 0x10; private static final int LOCLIKE_Y_INVERT = 0x20; private static final int LOCLIKE_Z_INVERT = 0x40; - private static final int LOCLIKE_OFFSET = 0x80; - + private static final int LOCLIKE_OFFSET = 0x80; + public ConstraintDefinitionLocLike(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - if(blenderContext.getBlenderKey().isFixUpAxis()) { - //swapping Y and X limits flag in the bitwise flag - int y = flag & LOCLIKE_Y; - int invY = flag & LOCLIKE_Y_INVERT; - int z = flag & LOCLIKE_Z; - int invZ = flag & LOCLIKE_Z_INVERT; - flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;//clear the other flags to swap them - flag |= y << 1; - flag |= invY << 1; - flag |= z >> 1; - flag |= invZ >> 1; - } - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - Vector3f ownerLocation = ownerTransform.getTranslation(); - Vector3f targetLocation = targetTransform.getTranslation(); - - Vector3f startLocation = ownerTransform.getTranslation().clone(); - Vector3f offset = Vector3f.ZERO; - if ((flag & LOCLIKE_OFFSET) != 0) {//we add the original location to the copied location - offset = startLocation; - } + super(constraintData, blenderContext); + if (blenderContext.getBlenderKey().isFixUpAxis()) { + // swapping Y and X limits flag in the bitwise flag + int y = flag & LOCLIKE_Y; + int invY = flag & LOCLIKE_Y_INVERT; + int z = flag & LOCLIKE_Z; + int invZ = flag & LOCLIKE_Z_INVERT; + flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;// clear the other flags to swap them + flag |= y << 1; + flag |= invY << 1; + flag |= z >> 1; + flag |= invZ >> 1; + } + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + Vector3f ownerLocation = ownerTransform.getTranslation(); + Vector3f targetLocation = targetTransform.getTranslation(); + + Vector3f startLocation = ownerTransform.getTranslation().clone(); + Vector3f offset = Vector3f.ZERO; + if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original location to the copied location + offset = startLocation; + } + + if ((flag & LOCLIKE_X) != 0) { + ownerLocation.x = targetLocation.x; + if ((flag & LOCLIKE_X_INVERT) != 0) { + ownerLocation.x = -ownerLocation.x; + } + } + if ((flag & LOCLIKE_Y) != 0) { + ownerLocation.y = targetLocation.y; + if ((flag & LOCLIKE_Y_INVERT) != 0) { + ownerLocation.y = -ownerLocation.y; + } + } + if ((flag & LOCLIKE_Z) != 0) { + ownerLocation.z = targetLocation.z; + if ((flag & LOCLIKE_Z_INVERT) != 0) { + ownerLocation.z = -ownerLocation.z; + } + } + ownerLocation.addLocal(offset); - if ((flag & LOCLIKE_X) != 0) { - ownerLocation.x = targetLocation.x; - if ((flag & LOCLIKE_X_INVERT) != 0) { - ownerLocation.x = -ownerLocation.x; - } - } - if ((flag & LOCLIKE_Y) != 0) { - ownerLocation.y = targetLocation.y; - if ((flag & LOCLIKE_Y_INVERT) != 0) { - ownerLocation.y = -ownerLocation.y; - } - } - if ((flag & LOCLIKE_Z) != 0) { - ownerLocation.z = targetLocation.z; - if ((flag & LOCLIKE_Z_INVERT) != 0) { - ownerLocation.z = -ownerLocation.z; - } - } - ownerLocation.addLocal(offset); - - if(influence < 1.0f) { - startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); - ownerLocation.addLocal(startLocation); - } - } + if (influence < 1.0f) { + startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); + ownerLocation.addLocal(startLocation); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java index db93ef994..abd2cfa11 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java @@ -9,67 +9,67 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Loc limit' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionLocLimit extends ConstraintDefinition { - private static final int LIMIT_XMIN = 0x01; +/* package */class ConstraintDefinitionLocLimit extends ConstraintDefinition { + private static final int LIMIT_XMIN = 0x01; private static final int LIMIT_XMAX = 0x02; private static final int LIMIT_YMIN = 0x04; private static final int LIMIT_YMAX = 0x08; private static final int LIMIT_ZMIN = 0x10; private static final int LIMIT_ZMAX = 0x20; - - protected float[][] limits = new float[3][2]; - + + protected float[][] limits = new float[3][2]; + public ConstraintDefinitionLocLimit(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - if(blenderContext.getBlenderKey().isFixUpAxis()) { - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - - //swapping Y and X limits flag in the bitwise flag - int ymin = flag & LIMIT_YMIN; - int ymax = flag & LIMIT_YMAX; - int zmin = flag & LIMIT_ZMIN; - int zmax = flag & LIMIT_ZMAX; - flag &= LIMIT_XMIN | LIMIT_XMAX;//clear the other flags to swap them - flag |= ymin << 2; - flag |= ymax << 2; - flag |= zmin >> 2; - flag |= zmax >> 2; - } else { - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - } - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - Vector3f translation = ownerTransform.getTranslation(); - - if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) { - translation.x -= (translation.x - limits[0][0]) * influence; - } - if ((flag & LIMIT_XMAX) != 0 && translation.x > limits[0][1]) { - translation.x -= (translation.x - limits[0][1]) * influence; - } - if ((flag & LIMIT_YMIN) != 0 && translation.y < limits[1][0]) { - translation.y -= (translation.y - limits[1][0]) * influence; - } - if ((flag & LIMIT_YMAX) != 0 && translation.y > limits[1][1]) { - translation.y -= (translation.y - limits[1][1]) * influence; - } - if ((flag & LIMIT_ZMIN) != 0 && translation.z < limits[2][0]) { - translation.z -= (translation.z - limits[2][0]) * influence; - } - if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) { - translation.z -= (translation.z - limits[2][1]) * influence; - } - } + super(constraintData, blenderContext); + if (blenderContext.getBlenderKey().isFixUpAxis()) { + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + + // swapping Y and X limits flag in the bitwise flag + int ymin = flag & LIMIT_YMIN; + int ymax = flag & LIMIT_YMAX; + int zmin = flag & LIMIT_ZMIN; + int zmax = flag & LIMIT_ZMAX; + flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap them + flag |= ymin << 2; + flag |= ymax << 2; + flag |= zmin >> 2; + flag |= zmax >> 2; + } else { + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + } + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + Vector3f translation = ownerTransform.getTranslation(); + + if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) { + translation.x -= (translation.x - limits[0][0]) * influence; + } + if ((flag & LIMIT_XMAX) != 0 && translation.x > limits[0][1]) { + translation.x -= (translation.x - limits[0][1]) * influence; + } + if ((flag & LIMIT_YMIN) != 0 && translation.y < limits[1][0]) { + translation.y -= (translation.y - limits[1][0]) * influence; + } + if ((flag & LIMIT_YMAX) != 0 && translation.y > limits[1][1]) { + translation.y -= (translation.y - limits[1][1]) * influence; + } + if ((flag & LIMIT_ZMIN) != 0 && translation.z < limits[2][0]) { + translation.z -= (translation.z - limits[2][0]) * influence; + } + if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) { + translation.z -= (translation.z - limits[2][1]) * influence; + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLockTrack.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLockTrack.java index 9e6746c46..a1a211981 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLockTrack.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLockTrack.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Action' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionLockTrack extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionLockTrack.class.getName()); - - public ConstraintDefinitionLockTrack(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Lock track' constraint - LOGGER.log(Level.WARNING, "'Lock track' constraint NOT implemented!"); - } +/* package */class ConstraintDefinitionLockTrack extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionLockTrack.class.getName()); + + public ConstraintDefinitionLockTrack(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Lock track' constraint + LOGGER.log(Level.WARNING, "'Lock track' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionMinMax.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionMinMax.java index b47b2d509..a0a84ac5c 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionMinMax.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionMinMax.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Min max' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionMinMax extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionMinMax.class.getName()); - - public ConstraintDefinitionMinMax(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Min max' constraint - LOGGER.log(Level.WARNING, "'Min max' constraint NOT implemented!"); - } +/* package */class ConstraintDefinitionMinMax extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionMinMax.class.getName()); + + public ConstraintDefinitionMinMax(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Min max' constraint + LOGGER.log(Level.WARNING, "'Min max' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java index bbecd9c52..7c558e3e9 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java @@ -8,14 +8,14 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Null' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionNull extends ConstraintDefinition { +/* package */class ConstraintDefinitionNull extends ConstraintDefinition { - public ConstraintDefinitionNull(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - //null constraint does nothing so no need to implement this one - } + public ConstraintDefinitionNull(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // null constraint does nothing so no need to implement this one + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionObjectSolver.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionObjectSolver.java index 0d47e5710..0727b9606 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionObjectSolver.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionObjectSolver.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Object solver' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionObjectSolver extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); - - public ConstraintDefinitionObjectSolver(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } +/* package */class ConstraintDefinitionObjectSolver extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Object solver' constraint - LOGGER.log(Level.WARNING, "'Object solver' constraint NOT implemented!"); - } + public ConstraintDefinitionObjectSolver(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Object solver' constraint + LOGGER.log(Level.WARNING, "'Object solver' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionPivot.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionPivot.java index 462d6681b..a2584c579 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionPivot.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionPivot.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * The pivot constraint. Available for blender 2.50+. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionPivot extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPivot.class.getName()); - - public ConstraintDefinitionPivot(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Pivot' constraint - LOGGER.log(Level.WARNING, "'Pivot' constraint NOT implemented!"); - } +/* package */class ConstraintDefinitionPivot extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPivot.class.getName()); + + public ConstraintDefinitionPivot(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Pivot' constraint + LOGGER.log(Level.WARNING, "'Pivot' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionPython.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionPython.java index 8ec9d652c..82b19eb4e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionPython.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionPython.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Python' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionPython extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPython.class.getName()); - - public ConstraintDefinitionPython(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Python' constraint - LOGGER.log(Level.WARNING, "'Python' constraint NOT implemented!"); - } +/* package */class ConstraintDefinitionPython extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionPython.class.getName()); + + public ConstraintDefinitionPython(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Python' constraint + LOGGER.log(Level.WARNING, "'Python' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRigidBodyJoint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRigidBodyJoint.java index 47dfa81a6..5cf98d767 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRigidBodyJoint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRigidBodyJoint.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Rigid body joint' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionRigidBodyJoint extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionRigidBodyJoint.class.getName()); - - public ConstraintDefinitionRigidBodyJoint(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Rigid body joint' constraint - LOGGER.log(Level.WARNING, "'Rigid body joint' constraint NOT implemented!"); - } +/* package */class ConstraintDefinitionRigidBodyJoint extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionRigidBodyJoint.class.getName()); + + public ConstraintDefinitionRigidBodyJoint(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Rigid body joint' constraint + LOGGER.log(Level.WARNING, "'Rigid body joint' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java index d3aa0f6b7..c01624466 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java @@ -9,59 +9,59 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Rot like' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionRotLike extends ConstraintDefinition { - private static final int ROTLIKE_X = 0x01; - private static final int ROTLIKE_Y = 0x02; - private static final int ROTLIKE_Z = 0x04; - private static final int ROTLIKE_X_INVERT = 0x10; - private static final int ROTLIKE_Y_INVERT = 0x20; - private static final int ROTLIKE_Z_INVERT = 0x40; - private static final int ROTLIKE_OFFSET = 0x80; - - private transient float[] ownerAngles = new float[3]; - private transient float[] targetAngles = new float[3]; - +/* package */class ConstraintDefinitionRotLike extends ConstraintDefinition { + private static final int ROTLIKE_X = 0x01; + private static final int ROTLIKE_Y = 0x02; + private static final int ROTLIKE_Z = 0x04; + private static final int ROTLIKE_X_INVERT = 0x10; + private static final int ROTLIKE_Y_INVERT = 0x20; + private static final int ROTLIKE_Z_INVERT = 0x40; + private static final int ROTLIKE_OFFSET = 0x80; + + private transient float[] ownerAngles = new float[3]; + private transient float[] targetAngles = new float[3]; + public ConstraintDefinitionRotLike(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } + super(constraintData, blenderContext); + } @Override public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - Quaternion ownerRotation = ownerTransform.getRotation(); - ownerAngles = ownerRotation.toAngles(ownerAngles); - targetAngles = targetTransform.getRotation().toAngles(targetAngles); - - Quaternion startRotation = ownerRotation.clone(); - Quaternion offset = Quaternion.IDENTITY; - if ((flag & ROTLIKE_OFFSET) != 0) {//we add the original rotation to the copied rotation - offset = startRotation; - } + Quaternion ownerRotation = ownerTransform.getRotation(); + ownerAngles = ownerRotation.toAngles(ownerAngles); + targetAngles = targetTransform.getRotation().toAngles(targetAngles); + + Quaternion startRotation = ownerRotation.clone(); + Quaternion offset = Quaternion.IDENTITY; + if ((flag & ROTLIKE_OFFSET) != 0) {// we add the original rotation to the copied rotation + offset = startRotation; + } + + if ((flag & ROTLIKE_X) != 0) { + ownerAngles[0] = targetAngles[0]; + if ((flag & ROTLIKE_X_INVERT) != 0) { + ownerAngles[0] = -ownerAngles[0]; + } + } + if ((flag & ROTLIKE_Y) != 0) { + ownerAngles[1] = targetAngles[1]; + if ((flag & ROTLIKE_Y_INVERT) != 0) { + ownerAngles[1] = -ownerAngles[1]; + } + } + if ((flag & ROTLIKE_Z) != 0) { + ownerAngles[2] = targetAngles[2]; + if ((flag & ROTLIKE_Z_INVERT) != 0) { + ownerAngles[2] = -ownerAngles[2]; + } + } + ownerRotation.fromAngles(ownerAngles).multLocal(offset); - if ((flag & ROTLIKE_X) != 0) { - ownerAngles[0] = targetAngles[0]; - if ((flag & ROTLIKE_X_INVERT) != 0) { - ownerAngles[0] = -ownerAngles[0]; - } - } - if ((flag & ROTLIKE_Y) != 0) { - ownerAngles[1] = targetAngles[1]; - if ((flag & ROTLIKE_Y_INVERT) != 0) { - ownerAngles[1] = -ownerAngles[1]; - } - } - if ((flag & ROTLIKE_Z) != 0) { - ownerAngles[2] = targetAngles[2]; - if ((flag & ROTLIKE_Z_INVERT) != 0) { - ownerAngles[2] = -ownerAngles[2]; - } - } - ownerRotation.fromAngles(ownerAngles).multLocal(offset); + if (influence < 1.0f) { - if(influence < 1.0f) { - -// startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); -// ownerLocation.addLocal(startLocation); - //TODO - } + // startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); + // ownerLocation.addLocal(startLocation); + // TODO + } } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java index 96028f277..80e18cbc6 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java @@ -11,75 +11,75 @@ import com.jme3.scene.plugins.blender.file.Structure; * @author Marcin Roguski (Kaelthas) */ /* package */class ConstraintDefinitionRotLimit extends ConstraintDefinition { - private static final int LIMIT_XROT = 0x01; - private static final int LIMIT_YROT = 0x02; - private static final int LIMIT_ZROT = 0x04; + private static final int LIMIT_XROT = 0x01; + private static final int LIMIT_YROT = 0x02; + private static final int LIMIT_ZROT = 0x04; - private transient float[][] limits = new float[3][2]; - private transient float[] angles = new float[3]; - - public ConstraintDefinitionRotLimit(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - if (blenderContext.getBlenderKey().isFixUpAxis()/* && owner.spatial != null*/) {//FIXME: !!!!!!!! - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + private transient float[][] limits = new float[3][2]; + private transient float[] angles = new float[3]; - // swapping Y and X limits flag in the bitwise flag - int limitY = flag & LIMIT_YROT; - int limitZ = flag & LIMIT_ZROT; - flag &= LIMIT_XROT;// clear the other flags to swap them - flag |= limitY << 1; - flag |= limitZ >> 1; - } else { - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - } + public ConstraintDefinitionRotLimit(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + if (blenderContext.getBlenderKey().isFixUpAxis()/* && owner.spatial != null */) {// FIXME: !!!!!!!! + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - // until blender 2.49 the rotations values were stored in degrees - if (blenderContext.getBlenderVersion() <= 249) { - for (int i = 0; i < limits.length; ++i) { - limits[i][0] *= FastMath.DEG_TO_RAD; - limits[i][1] *= FastMath.DEG_TO_RAD; - } - } - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - if ((flag & LIMIT_XROT) != 0) { - float difference = 0.0f; - if (angles[0] < limits[0][0]) { - difference = (angles[0] - limits[0][0]) * influence; - } else if (angles[0] > limits[0][1]) { - difference = (angles[0] - limits[0][1]) * influence; - } - angles[0] -= difference; - } - if ((flag & LIMIT_YROT) != 0) { - float difference = 0.0f; - if (angles[1] < limits[1][0]) { - difference = (angles[1] - limits[1][0]) * influence; - } else if (angles[1] > limits[1][1]) { - difference = (angles[1] - limits[1][1]) * influence; - } - angles[1] -= difference; - } - if ((flag & LIMIT_ZROT) != 0) { - float difference = 0.0f; - if (angles[2] < limits[2][0]) { - difference = (angles[2] - limits[2][0]) * influence; - } else if (angles[2] > limits[2][1]) { - difference = (angles[2] - limits[2][1]) * influence; - } - angles[2] -= difference; - } - } + // swapping Y and X limits flag in the bitwise flag + int limitY = flag & LIMIT_YROT; + int limitZ = flag & LIMIT_ZROT; + flag &= LIMIT_XROT;// clear the other flags to swap them + flag |= limitY << 1; + flag |= limitZ >> 1; + } else { + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + } + + // until blender 2.49 the rotations values were stored in degrees + if (blenderContext.getBlenderVersion() <= 249) { + for (int i = 0; i < limits.length; ++i) { + limits[i][0] *= FastMath.DEG_TO_RAD; + limits[i][1] *= FastMath.DEG_TO_RAD; + } + } + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + if ((flag & LIMIT_XROT) != 0) { + float difference = 0.0f; + if (angles[0] < limits[0][0]) { + difference = (angles[0] - limits[0][0]) * influence; + } else if (angles[0] > limits[0][1]) { + difference = (angles[0] - limits[0][1]) * influence; + } + angles[0] -= difference; + } + if ((flag & LIMIT_YROT) != 0) { + float difference = 0.0f; + if (angles[1] < limits[1][0]) { + difference = (angles[1] - limits[1][0]) * influence; + } else if (angles[1] > limits[1][1]) { + difference = (angles[1] - limits[1][1]) * influence; + } + angles[1] -= difference; + } + if ((flag & LIMIT_ZROT) != 0) { + float difference = 0.0f; + if (angles[2] < limits[2][0]) { + difference = (angles[2] - limits[2][0]) * influence; + } else if (angles[2] > limits[2][1]) { + difference = (angles[2] - limits[2][1]) * influence; + } + angles[2] -= difference; + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSameVolume.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSameVolume.java index 7812e3e2f..224aebb7e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSameVolume.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSameVolume.java @@ -12,16 +12,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionSameVolume extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSameVolume.class.getName()); - - public ConstraintDefinitionSameVolume(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Same volume' constraint - LOGGER.log(Level.WARNING, "'Same volume' constraint NOT implemented!"); - } +/* package */class ConstraintDefinitionSameVolume extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSameVolume.class.getName()); + + public ConstraintDefinitionSameVolume(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Same volume' constraint + LOGGER.log(Level.WARNING, "'Same volume' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionShrinkWrap.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionShrinkWrap.java index d1e4c5f52..0c9afec8e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionShrinkWrap.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionShrinkWrap.java @@ -8,55 +8,54 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Shrink wrap' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionShrinkWrap extends ConstraintDefinition { - - public ConstraintDefinitionShrinkWrap(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } +/* package */class ConstraintDefinitionShrinkWrap extends ConstraintDefinition { - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - //loading mesh points (blender ensures that the target is a mesh-object) - /*List pts = new ArrayList(); - Node target = (Node) this.target.getObject(); - for(Spatial spatial : target.getChildren()) { - if(spatial instanceof Geometry) { - Mesh mesh = ((Geometry) spatial).getMesh(); - FloatBuffer floatBuffer = mesh.getFloatBuffer(Type.Position); - for(int i=0;i pts = new ArrayList(); + * Node target = (Node) this.target.getObject(); + * for(Spatial spatial : target.getChildren()) { + * if(spatial instanceof Geometry) { + * Mesh mesh = ((Geometry) spatial).getMesh(); + * FloatBuffer floatBuffer = mesh.getFloatBuffer(Type.Position); + * for(int i=0;i> 1; - } - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - Vector3f ownerScale = ownerTransform.getScale(); - Vector3f targetScale = targetTransform.getScale(); - - Vector3f offset = Vector3f.ZERO; - if ((flag & LOCLIKE_OFFSET) != 0) {//we add the original scale to the copied scale - offset = ownerScale.clone(); - } +/* package */class ConstraintDefinitionSizeLike extends ConstraintDefinition { + private static final int SIZELIKE_X = 0x01; + private static final int SIZELIKE_Y = 0x02; + private static final int SIZELIKE_Z = 0x04; + private static final int LOCLIKE_OFFSET = 0x80; - if ((flag & SIZELIKE_X) != 0) { - ownerScale.x = targetScale.x * influence + (1.0f - influence) * ownerScale.x; - } - if ((flag & SIZELIKE_Y) != 0) { - ownerScale.y = targetScale.y * influence + (1.0f - influence) * ownerScale.y; - } - if ((flag & SIZELIKE_Z) != 0) { - ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z; - } - ownerScale.addLocal(offset); - } + public ConstraintDefinitionSizeLike(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + if (blenderContext.getBlenderKey().isFixUpAxis()) { + // swapping Y and X limits flag in the bitwise flag + int y = flag & SIZELIKE_Y; + int z = flag & SIZELIKE_Z; + flag &= SIZELIKE_X | LOCLIKE_OFFSET;// clear the other flags to swap them + flag |= y << 1; + flag |= z >> 1; + } + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + Vector3f ownerScale = ownerTransform.getScale(); + Vector3f targetScale = targetTransform.getScale(); + + Vector3f offset = Vector3f.ZERO; + if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original scale to the copied scale + offset = ownerScale.clone(); + } + + if ((flag & SIZELIKE_X) != 0) { + ownerScale.x = targetScale.x * influence + (1.0f - influence) * ownerScale.x; + } + if ((flag & SIZELIKE_Y) != 0) { + ownerScale.y = targetScale.y * influence + (1.0f - influence) * ownerScale.y; + } + if ((flag & SIZELIKE_Z) != 0) { + ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z; + } + ownerScale.addLocal(offset); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java index f8db11219..9796fdbf2 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java @@ -9,67 +9,67 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Size limit' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionSizeLimit extends ConstraintDefinition { - private static final int LIMIT_XMIN = 0x01; - private static final int LIMIT_XMAX = 0x02; - private static final int LIMIT_YMIN = 0x04; - private static final int LIMIT_YMAX = 0x08; - private static final int LIMIT_ZMIN = 0x10; - private static final int LIMIT_ZMAX = 0x20; - - protected transient float[][] limits = new float[3][2]; - - public ConstraintDefinitionSizeLimit(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - if(blenderContext.getBlenderKey().isFixUpAxis()) { - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - - //swapping Y and X limits flag in the bitwise flag - int ymin = flag & LIMIT_YMIN; - int ymax = flag & LIMIT_YMAX; - int zmin = flag & LIMIT_ZMIN; - int zmax = flag & LIMIT_ZMAX; - flag &= LIMIT_XMIN | LIMIT_XMAX;//clear the other flags to swap them - flag |= ymin << 2; - flag |= ymax << 2; - flag |= zmin >> 2; - flag |= zmax >> 2; - } else { - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - } - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - Vector3f scale = ownerTransform.getScale(); - - if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) { - scale.x -= (scale.x - limits[0][0]) * influence; - } - if ((flag & LIMIT_XMAX) != 0 && scale.x > limits[0][1]) { - scale.x -= (scale.x - limits[0][1]) * influence; - } - if ((flag & LIMIT_YMIN) != 0 && scale.y < limits[1][0]) { - scale.y -= (scale.y - limits[1][0]) * influence; - } - if ((flag & LIMIT_YMAX) != 0 && scale.y > limits[1][1]) { - scale.y -= (scale.y - limits[1][1]) * influence; - } - if ((flag & LIMIT_ZMIN) != 0 && scale.z < limits[2][0]) { - scale.z -= (scale.z - limits[2][0]) * influence; - } - if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) { - scale.z -= (scale.z - limits[2][1]) * influence; - } - } +/* package */class ConstraintDefinitionSizeLimit extends ConstraintDefinition { + private static final int LIMIT_XMIN = 0x01; + private static final int LIMIT_XMAX = 0x02; + private static final int LIMIT_YMIN = 0x04; + private static final int LIMIT_YMAX = 0x08; + private static final int LIMIT_ZMIN = 0x10; + private static final int LIMIT_ZMAX = 0x20; + + protected transient float[][] limits = new float[3][2]; + + public ConstraintDefinitionSizeLimit(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + if (blenderContext.getBlenderKey().isFixUpAxis()) { + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + + // swapping Y and X limits flag in the bitwise flag + int ymin = flag & LIMIT_YMIN; + int ymax = flag & LIMIT_YMAX; + int zmin = flag & LIMIT_ZMIN; + int zmax = flag & LIMIT_ZMAX; + flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap them + flag |= ymin << 2; + flag |= ymax << 2; + flag |= zmin >> 2; + flag |= zmax >> 2; + } else { + limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); + limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); + limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); + limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); + } + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + Vector3f scale = ownerTransform.getScale(); + + if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) { + scale.x -= (scale.x - limits[0][0]) * influence; + } + if ((flag & LIMIT_XMAX) != 0 && scale.x > limits[0][1]) { + scale.x -= (scale.x - limits[0][1]) * influence; + } + if ((flag & LIMIT_YMIN) != 0 && scale.y < limits[1][0]) { + scale.y -= (scale.y - limits[1][0]) * influence; + } + if ((flag & LIMIT_YMAX) != 0 && scale.y > limits[1][1]) { + scale.y -= (scale.y - limits[1][1]) * influence; + } + if ((flag & LIMIT_ZMIN) != 0 && scale.z < limits[2][0]) { + scale.z -= (scale.z - limits[2][0]) * influence; + } + if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) { + scale.z -= (scale.z - limits[2][1]) * influence; + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSplineInverseKinematic.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSplineInverseKinematic.java index 424fd0f52..b1d3cee9e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSplineInverseKinematic.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSplineInverseKinematic.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * The spline inverse kinematic constraint. Available for blender 2.50+. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionSplineInverseKinematic extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSplineInverseKinematic.class.getName()); - - public ConstraintDefinitionSplineInverseKinematic(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO Auto-generated method stub - LOGGER.log(Level.WARNING, "'Splie IK' constraint NOT implemented!"); - } +/* package */class ConstraintDefinitionSplineInverseKinematic extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionSplineInverseKinematic.class.getName()); + + public ConstraintDefinitionSplineInverseKinematic(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO Auto-generated method stub + LOGGER.log(Level.WARNING, "'Splie IK' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionStretchTo.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionStretchTo.java index 47fac26fb..6459ddefe 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionStretchTo.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionStretchTo.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Stretch to' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionStretchTo extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionStretchTo.class.getName()); - - public ConstraintDefinitionStretchTo(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } +/* package */class ConstraintDefinitionStretchTo extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionStretchTo.class.getName()); - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Stretch to' constraint - LOGGER.log(Level.WARNING, "'Stretch to' constraint NOT implemented!"); - } + public ConstraintDefinitionStretchTo(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Stretch to' constraint + LOGGER.log(Level.WARNING, "'Stretch to' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTrackTo.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTrackTo.java index 4a5dc2620..9e2e0e757 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTrackTo.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTrackTo.java @@ -13,15 +13,15 @@ import com.jme3.scene.plugins.blender.file.Structure; * @author Marcin Roguski (Kaelthas) */ /* package */class ConstraintDefinitionTrackTo extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionTrackTo.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionTrackTo.class.getName()); - public ConstraintDefinitionTrackTo(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Track to' constraint - LOGGER.log(Level.WARNING, "'Track to' constraint NOT implemented!"); - } + public ConstraintDefinitionTrackTo(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Track to' constraint + LOGGER.log(Level.WARNING, "'Track to' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java index 01d9a5c01..1b2bcdce1 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java @@ -12,16 +12,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionTransLike extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionTransLike.class.getName()); - - public ConstraintDefinitionTransLike(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } - - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Trans like' constraint - LOGGER.log(Level.WARNING, "'Trans like' constraint NOT implemented!"); - } +/* package */class ConstraintDefinitionTransLike extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionTransLike.class.getName()); + + public ConstraintDefinitionTransLike(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Trans like' constraint + LOGGER.log(Level.WARNING, "'Trans like' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransform.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransform.java index bc27fc5b9..4042b6111 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransform.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransform.java @@ -11,16 +11,16 @@ import com.jme3.scene.plugins.blender.file.Structure; * This class represents 'Transform' constraint type in blender. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ConstraintDefinitionTransform extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); - - public ConstraintDefinitionTransform(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - } +/* package */class ConstraintDefinitionTransform extends ConstraintDefinition { + private static final Logger LOGGER = Logger.getLogger(ConstraintDefinitionAction.class.getName()); - @Override - public void bake(Transform ownerTransform, Transform targetTransform, float influence) { - // TODO: implement 'Transform' constraint - LOGGER.log(Level.WARNING, "'Transform' constraint NOT implemented!"); - } + public ConstraintDefinitionTransform(Structure constraintData, BlenderContext blenderContext) { + super(constraintData, blenderContext); + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + // TODO: implement 'Transform' constraint + LOGGER.log(Level.WARNING, "'Transform' constraint NOT implemented!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java b/engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java index 3bbbd950d..3034e1789 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java @@ -16,17 +16,17 @@ public class BezierCurve { public static final int X_VALUE = 0; public static final int Y_VALUE = 1; public static final int Z_VALUE = 2; - /** - * The type of the curve. Describes the data it modifies. + /** + * The type of the curve. Describes the data it modifies. * Used in ipos calculations. */ - private int type; + private int type; /** The dimension of the curve. */ - private int dimension; + private int dimension; /** A table of the bezier points. */ - private float[][][] bezierPoints; + private float[][][] bezierPoints; /** Array that stores a radius for each bezier triple. */ - private float[] radiuses; + private float[] radiuses; @SuppressWarnings("unchecked") public BezierCurve(final int type, final List bezTriples, final int dimension) { @@ -35,9 +35,9 @@ public class BezierCurve { } this.type = type; this.dimension = dimension; - //first index of the bezierPoints table has the length of triples amount - //the second index points to a table od three points of a bezier triple (handle, point, handle) - //the third index specifies the coordinates of the specific point in a bezier triple + // first index of the bezierPoints table has the length of triples amount + // the second index points to a table od three points of a bezier triple (handle, point, handle) + // the third index specifies the coordinates of the specific point in a bezier triple bezierPoints = new float[bezTriples.size()][3][dimension]; radiuses = new float[bezTriples.size()]; int i = 0, j, k; @@ -48,18 +48,18 @@ public class BezierCurve { bezierPoints[i][j][k] = vec.get(j, k).floatValue(); } } - radiuses[i++] = ((Number)bezTriple.getFieldValue("radius")).floatValue(); + radiuses[i++] = ((Number) bezTriple.getFieldValue("radius")).floatValue(); } } /** * This method evaluates the data for the specified frame. The Y value is returned. * @param frame - * the frame for which the value is being calculated + * the frame for which the value is being calculated * @param valuePart - * this param specifies wheather we should return the X, Y or Z part of the result value; it should have - * one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result - * Z_VALUE - the Z factor of the result + * this param specifies wheather we should return the X, Y or Z part of the result value; it should have + * one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result + * Z_VALUE - the Z factor of the result * @return the value of the curve */ public float evaluate(int frame, int valuePart) { @@ -74,7 +74,7 @@ public class BezierCurve { } if (frame < bezierPoints[0][1][0]) { return bezierPoints[0][1][1]; - } else { //frame>bezierPoints[bezierPoints.length-1][1][0] + } else { // frame>bezierPoints[bezierPoints.length-1][1][0] return bezierPoints[bezierPoints.length - 1][1][1]; } } @@ -96,17 +96,17 @@ public class BezierCurve { return type; } - /** - * The method returns the radius for the required bezier triple. - * - * @param bezierTripleIndex - * index of the bezier triple - * @return radius of the required bezier triple - */ - public float getRadius(int bezierTripleIndex) { - return radiuses[bezierTripleIndex]; - } - + /** + * The method returns the radius for the required bezier triple. + * + * @param bezierTripleIndex + * index of the bezier triple + * @return radius of the required bezier triple + */ + public float getRadius(int bezierTripleIndex) { + return radiuses[bezierTripleIndex]; + } + /** * This method returns a list of control points for this curve. * @return a list of control points for this curve. @@ -133,18 +133,14 @@ public class BezierCurve { /** * This method converts the bezier triple of a specified index into text. * @param tripleIndex - * index of the triple + * index of the triple * @return text representation of the triple */ private String toStringBezTriple(int tripleIndex) { if (this.dimension == 2) { - return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") (" - + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") (" - + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]"; + return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]"; } else { - return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") (" - + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") (" - + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]"; + return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]"; } } } \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java index 08aabb87a..2171e12ec 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java @@ -77,20 +77,20 @@ import com.jme3.util.BufferUtils; * @author Marcin Roguski (Kaelthas) */ public class CurvesHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(CurvesHelper.class.getName()); - + private static final Logger LOGGER = Logger.getLogger(CurvesHelper.class.getName()); + /** Minimum basis U function degree for NURBS curves and surfaces. */ - protected int minimumBasisUFunctionDegree = 4; + protected int minimumBasisUFunctionDegree = 4; /** Minimum basis V function degree for NURBS curves and surfaces. */ - protected int minimumBasisVFunctionDegree = 4; + protected int minimumBasisVFunctionDegree = 4; /** * This constructor parses the given blender version and stores the result. Some functionalities may differ in * different blender versions. * @param blenderVersion - * the version read from the blend file + * the version read from the blend file * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not + * a variable that indicates if the Y asxis is the UP axis or not */ public CurvesHelper(String blenderVersion, boolean fixUpAxis) { super(blenderVersion, fixUpAxis); @@ -113,13 +113,13 @@ public class CurvesHelper extends AbstractBlenderHelper { boolean isFront = (flag & 0x02) != 0 && !is3D; boolean isBack = (flag & 0x04) != 0 && !is3D; if (isFront) { - LOGGER.warning("No front face in curve implemented yet!");//TODO: implement front face + LOGGER.warning("No front face in curve implemented yet!");// TODO: implement front face } if (isBack) { - LOGGER.warning("No back face in curve implemented yet!");//TODO: implement back face + LOGGER.warning("No back face in curve implemented yet!");// TODO: implement back face } - - //reading nurbs (and sorting them by material) + + // reading nurbs (and sorting them by material) List nurbStructures = ((Structure) curveStructure.getFieldValue("nurb")).evaluateListBase(blenderContext); Map> nurbs = new HashMap>(); for (Structure nurb : nurbStructures) { @@ -132,20 +132,20 @@ public class CurvesHelper extends AbstractBlenderHelper { nurbList.add(nurb); } - //getting materials + // getting materials MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); MaterialContext[] materialContexts = materialHelper.getMaterials(curveStructure, blenderContext); Material defaultMaterial = null; if (materialContexts != null) { - for (MaterialContext materialContext : materialContexts) { - materialContext.setFaceCullMode(FaceCullMode.Off); - } + for (MaterialContext materialContext : materialContexts) { + materialContext.setFaceCullMode(FaceCullMode.Off); + } } else { - defaultMaterial = blenderContext.getDefaultMaterial().clone(); - defaultMaterial.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off); + defaultMaterial = blenderContext.getDefaultMaterial().clone(); + defaultMaterial.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off); } - //getting or creating bevel object + // getting or creating bevel object List bevelObject = null; Pointer pBevelObject = (Pointer) curveStructure.getFieldValue("bevobj"); if (pBevelObject.isNotNull()) { @@ -161,50 +161,48 @@ public class CurvesHelper extends AbstractBlenderHelper { List conrtolPoints = new ArrayList(extrude > 0.0f ? 19 : 13); if (extrude > 0.0f) { - conrtolPoints.add(new Vector3f(-bevelDepth, 0, extrude)); - conrtolPoints.add(new Vector3f(-bevelDepth, 0, -handlerLength + extrude)); + conrtolPoints.add(new Vector3f(-bevelDepth, 0, extrude)); + conrtolPoints.add(new Vector3f(-bevelDepth, 0, -handlerLength + extrude)); conrtolPoints.add(new Vector3f(-bevelDepth, 0, handlerLength - extrude)); } - + conrtolPoints.add(new Vector3f(-bevelDepth, 0, -extrude)); conrtolPoints.add(new Vector3f(-bevelDepth, 0, -handlerLength - extrude)); - + conrtolPoints.add(new Vector3f(-handlerLength, 0, -bevelDepth - extrude)); conrtolPoints.add(new Vector3f(0, 0, -bevelDepth - extrude)); conrtolPoints.add(new Vector3f(handlerLength, 0, -bevelDepth - extrude)); - + if (extrude > 0.0f) { - conrtolPoints.add(new Vector3f(bevelDepth, 0, -extrude - handlerLength)); - conrtolPoints.add(new Vector3f(bevelDepth, 0, -extrude)); + conrtolPoints.add(new Vector3f(bevelDepth, 0, -extrude - handlerLength)); + conrtolPoints.add(new Vector3f(bevelDepth, 0, -extrude)); conrtolPoints.add(new Vector3f(bevelDepth, 0, -extrude + handlerLength)); } - + conrtolPoints.add(new Vector3f(bevelDepth, 0, extrude - handlerLength)); conrtolPoints.add(new Vector3f(bevelDepth, 0, extrude)); conrtolPoints.add(new Vector3f(bevelDepth, 0, extrude + handlerLength)); - + conrtolPoints.add(new Vector3f(handlerLength, 0, bevelDepth + extrude)); conrtolPoints.add(new Vector3f(0, 0, bevelDepth + extrude)); conrtolPoints.add(new Vector3f(-handlerLength, 0, bevelDepth + extrude)); - + conrtolPoints.add(new Vector3f(-bevelDepth, 0, handlerLength + extrude)); conrtolPoints.add(new Vector3f(-bevelDepth, 0, extrude)); - + Spline bevelSpline = new Spline(SplineType.Bezier, conrtolPoints, 0, false); Curve bevelCurve = new Curve(bevelSpline, bevResol); bevelObject = new ArrayList(1); bevelObject.add(new Geometry("", bevelCurve)); } else if (extrude > 0.0f) { - Spline bevelSpline = new Spline(SplineType.Linear, new Vector3f[]{ - new Vector3f(0, 0, -extrude), new Vector3f(0, 0, extrude) - }, 1, false); + Spline bevelSpline = new Spline(SplineType.Linear, new Vector3f[] { new Vector3f(0, 0, -extrude), new Vector3f(0, 0, extrude) }, 1, false); Curve bevelCurve = new Curve(bevelSpline, bevResol); bevelObject = new ArrayList(1); bevelObject.add(new Geometry("", bevelCurve)); } } - //getting taper object + // getting taper object Spline taperObject = null; Pointer pTaperObject = (Pointer) curveStructure.getFieldValue("taperobj"); if (bevelObject != null && pTaperObject.isNotNull()) { @@ -214,40 +212,40 @@ public class CurvesHelper extends AbstractBlenderHelper { } Vector3f loc = this.getLoc(curveStructure); - //creating the result curves + // creating the result curves List result = new ArrayList(nurbs.size()); for (Entry> nurbEntry : nurbs.entrySet()) { for (Structure nurb : nurbEntry.getValue()) { int type = ((Number) nurb.getFieldValue("type")).intValue(); List nurbGeoms = null; - if ((type & 0x01) != 0) {//Bezier curve + if ((type & 0x01) != 0) {// Bezier curve nurbGeoms = this.loadBezierCurve(loc, nurb, bevelObject, taperObject, blenderContext); - } else if ((type & 0x04) != 0) {//NURBS + } else if ((type & 0x04) != 0) {// NURBS nurbGeoms = this.loadNurb(loc, nurb, bevelObject, taperObject, blenderContext); } - if (nurbGeoms != null) {//setting the name and assigning materials + if (nurbGeoms != null) {// setting the name and assigning materials for (Geometry nurbGeom : nurbGeoms) { - if(materialContexts != null) { - materialContexts[nurbEntry.getKey().intValue()].applyMaterial(nurbGeom, curveStructure.getOldMemoryAddress(), null, blenderContext); - } else { - nurbGeom.setMaterial(defaultMaterial); - } + if (materialContexts != null) { + materialContexts[nurbEntry.getKey().intValue()].applyMaterial(nurbGeom, curveStructure.getOldMemoryAddress(), null, blenderContext); + } else { + nurbGeom.setMaterial(defaultMaterial); + } nurbGeom.setName(name); result.add(nurbGeom); } } } } - - //reading custom properties - if(blenderContext.getBlenderKey().isLoadObjectProperties()) { - Properties properties = this.loadProperties(curveStructure, blenderContext); - //the loaded property is a group property, so we need to get each value and set it to Spatial - if(result instanceof Spatial && properties != null && properties.getValue() != null) { - this.applyProperties((Spatial) result, properties); - } - } - + + // reading custom properties + if (blenderContext.getBlenderKey().isLoadObjectProperties()) { + Properties properties = this.loadProperties(curveStructure, blenderContext); + // the loaded property is a group property, so we need to get each value and set it to Spatial + if (result instanceof Spatial && properties != null && properties.getValue() != null) { + this.applyProperties((Spatial) result, properties); + } + } + return result; } @@ -267,8 +265,7 @@ public class CurvesHelper extends AbstractBlenderHelper { * @throws BlenderFileException * an exception is thrown when there are problems with the blender file */ - protected List loadBezierCurve(Vector3f loc, Structure nurb, List bevelObject, Spline taperObject, - BlenderContext blenderContext) throws BlenderFileException { + protected List loadBezierCurve(Vector3f loc, Structure nurb, List bevelObject, Spline taperObject, BlenderContext blenderContext) throws BlenderFileException { Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt"); List result = new ArrayList(); if (pBezierTriple.isNotNull()) { @@ -276,45 +273,45 @@ public class CurvesHelper extends AbstractBlenderHelper { int resolution = ((Number) nurb.getFieldValue("resolu")).intValue(); boolean cyclic = (((Number) nurb.getFieldValue("flagu")).intValue() & 0x01) != 0; - //creating the curve object + // creating the curve object BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(blenderContext.getInputStream()), 3); List controlPoints = bezierCurve.getControlPoints(); - if(fixUpAxis) { - for(Vector3f v : controlPoints) { - float y = v.y; - v.y = v.z; - v.z = -y; - } + if (fixUpAxis) { + for (Vector3f v : controlPoints) { + float y = v.y; + v.y = v.z; + v.z = -y; + } + } + + if (bevelObject != null && taperObject == null) {// create taper object using the scales of the bezier triple + int triplesCount = controlPoints.size() / 3; + List taperControlPoints = new ArrayList(triplesCount); + for (int i = 0; i < triplesCount; ++i) { + taperControlPoints.add(new Vector3f(controlPoints.get(i * 3 + 1).x, bezierCurve.getRadius(i), 0)); + } + taperObject = new Spline(SplineType.Linear, taperControlPoints, 0, false); } - - if (bevelObject != null && taperObject == null) {// create taper object using the scales of the bezier triple - int triplesCount = controlPoints.size() / 3; - List taperControlPoints = new ArrayList(triplesCount); - for (int i = 0; i < triplesCount; ++i) { - taperControlPoints.add(new Vector3f(controlPoints.get(i * 3 + 1).x, bezierCurve.getRadius(i), 0)); - } - taperObject = new Spline(SplineType.Linear, taperControlPoints, 0, false); - } if (cyclic) { - //copy the first three points at the end + // copy the first three points at the end for (int i = 0; i < 3; ++i) { controlPoints.add(controlPoints.get(i)); } } - //removing the first and last handles + // removing the first and last handles controlPoints.remove(0); controlPoints.remove(controlPoints.size() - 1); - //creating curve + // creating curve Spline spline = new Spline(SplineType.Bezier, controlPoints, 0, false); Curve curve = new Curve(spline, resolution); - if (bevelObject == null) {//creating a normal curve + if (bevelObject == null) {// creating a normal curve Geometry curveGeometry = new Geometry(null, curve); result.add(curveGeometry); - //TODO: use front and back flags; surface excluding algorithm for bezier circles should be added - } else {//creating curve with bevel and taper shape - result = this.applyBevelAndTaper(curve, bevelObject, taperObject, smooth, blenderContext); + // TODO: use front and back flags; surface excluding algorithm for bezier circles should be added + } else {// creating curve with bevel and taper shape + result = this.applyBevelAndTaper(curve, bevelObject, taperObject, smooth, blenderContext); } } return result; @@ -337,11 +334,10 @@ public class CurvesHelper extends AbstractBlenderHelper { * an exception is throw when problems with blender loaded data occurs */ @SuppressWarnings("unchecked") - protected List loadNurb(Vector3f loc, Structure nurb, List bevelObject, Spline taperObject, - BlenderContext blenderContext) throws BlenderFileException { - //loading the knots + protected List loadNurb(Vector3f loc, Structure nurb, List bevelObject, Spline taperObject, BlenderContext blenderContext) throws BlenderFileException { + // loading the knots List[] knots = new List[2]; - Pointer[] pKnots = new Pointer[]{(Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv")}; + Pointer[] pKnots = new Pointer[] { (Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv") }; for (int i = 0; i < knots.length; ++i) { if (pKnots[i].isNotNull()) { FileBlockHeader fileBlockHeader = blenderContext.getFileBlock(pKnots[i].getOldMemoryAddress()); @@ -355,13 +351,13 @@ public class CurvesHelper extends AbstractBlenderHelper { } } - //loading the flags and orders (basis functions degrees) + // loading the flags and orders (basis functions degrees) int flagU = ((Number) nurb.getFieldValue("flagu")).intValue(); int flagV = ((Number) nurb.getFieldValue("flagv")).intValue(); int orderU = ((Number) nurb.getFieldValue("orderu")).intValue(); int orderV = ((Number) nurb.getFieldValue("orderv")).intValue(); - //loading control points and their weights + // loading control points and their weights int pntsU = ((Number) nurb.getFieldValue("pntsu")).intValue(); int pntsV = ((Number) nurb.getFieldValue("pntsv")).intValue(); List bPoints = ((Pointer) nurb.getFieldValue("bp")).fetchData(blenderContext.getInputStream()); @@ -391,17 +387,17 @@ public class CurvesHelper extends AbstractBlenderHelper { int resolu = ((Number) nurb.getFieldValue("resolu")).intValue() + 1; List result; - if (knots[1] == null) {//creating the curve + if (knots[1] == null) {// creating the curve Spline nurbSpline = new Spline(controlPoints.get(0), knots[0]); Curve nurbCurve = new Curve(nurbSpline, resolu); if (bevelObject != null) { - result = this.applyBevelAndTaper(nurbCurve, bevelObject, taperObject, true, blenderContext);//TODO: smooth + result = this.applyBevelAndTaper(nurbCurve, bevelObject, taperObject, true, blenderContext);// TODO: smooth } else { result = new ArrayList(1); Geometry nurbGeometry = new Geometry("", nurbCurve); result.add(nurbGeometry); } - } else {//creating the nurb surface + } else {// creating the nurb surface int resolv = ((Number) nurb.getFieldValue("resolv")).intValue() + 1; Surface nurbSurface = Surface.createNurbsSurface(controlPoints, knots, resolu, resolv, orderU, orderV); Geometry nurbGeometry = new Geometry("", nurbSurface); @@ -410,44 +406,44 @@ public class CurvesHelper extends AbstractBlenderHelper { } return result; } - - /** - * The method computes the taper scale on the given point on the curve. - * - * @param taper - * the taper object that defines the scale - * @param percent - * the percent of the 'road' along the curve - * @return scale on the pointed place along the curve - */ - protected float getTaperScale(Spline taper, float percent) { - if(taper == null) { - return 1;//return scale = 1 if no taper is applied - } - percent = FastMath.clamp(percent, 0, 1); - List segmentLengths = taper.getSegmentsLength(); - float percentLength = taper.getTotalLength() * percent; - float partLength = 0; - int i; - for (i = 0; i < segmentLengths.size(); ++i) { - partLength += segmentLengths.get(i); - if (partLength > percentLength) { - partLength -= segmentLengths.get(i); - percentLength -= partLength; - percent = percentLength / segmentLengths.get(i); - break; - } - } - // do not cross the line :) - if (percent >= 1) { - percent = 1; - --i; - } - if (taper.getType() == SplineType.Bezier) { - i *= 3; - } - return taper.interpolate(percent, i, null).y; - } + + /** + * The method computes the taper scale on the given point on the curve. + * + * @param taper + * the taper object that defines the scale + * @param percent + * the percent of the 'road' along the curve + * @return scale on the pointed place along the curve + */ + protected float getTaperScale(Spline taper, float percent) { + if (taper == null) { + return 1;// return scale = 1 if no taper is applied + } + percent = FastMath.clamp(percent, 0, 1); + List segmentLengths = taper.getSegmentsLength(); + float percentLength = taper.getTotalLength() * percent; + float partLength = 0; + int i; + for (i = 0; i < segmentLengths.size(); ++i) { + partLength += segmentLengths.get(i); + if (partLength > percentLength) { + partLength -= segmentLengths.get(i); + percentLength -= partLength; + percent = percentLength / segmentLengths.get(i); + break; + } + } + // do not cross the line :) + if (percent >= 1) { + percent = 1; + --i; + } + if (taper.getType() == SplineType.Bezier) { + i *= 3; + } + return taper.interpolate(percent, i, null).y; + } /** * This method applies bevel and taper objects to the curve. @@ -458,115 +454,114 @@ public class CurvesHelper extends AbstractBlenderHelper { * @param taperObject * the taper object * @param smooth - * the smooth flag + * the smooth flag * @param blenderContext * the blender context * @return a list of geometries representing the beveled and/or tapered curve */ - protected List applyBevelAndTaper(Curve curve, List bevelObject, Spline taperObject, - boolean smooth, BlenderContext blenderContext) { - Vector3f[] curvePoints = BufferUtils.getVector3Array(curve.getFloatBuffer(Type.Position)); - Vector3f subtractResult = new Vector3f(); + protected List applyBevelAndTaper(Curve curve, List bevelObject, Spline taperObject, boolean smooth, BlenderContext blenderContext) { + Vector3f[] curvePoints = BufferUtils.getVector3Array(curve.getFloatBuffer(Type.Position)); + Vector3f subtractResult = new Vector3f(); float curveLength = curve.getLength(); - + FloatBuffer[] vertexBuffers = new FloatBuffer[bevelObject.size()]; FloatBuffer[] normalBuffers = new FloatBuffer[bevelObject.size()]; IndexBuffer[] indexBuffers = new IndexBuffer[bevelObject.size()]; for (int geomIndex = 0; geomIndex < bevelObject.size(); ++geomIndex) { - Mesh mesh = bevelObject.get(geomIndex).getMesh(); + Mesh mesh = bevelObject.get(geomIndex).getMesh(); Vector3f[] positions = BufferUtils.getVector3Array(mesh.getFloatBuffer(Type.Position)); Vector3f[] bevelPoints = this.transformToFirstLineOfBevelPoints(positions, curvePoints[0], curvePoints[1]); - + List bevels = new ArrayList(curvePoints.length); bevels.add(bevelPoints); - + vertexBuffers[geomIndex] = BufferUtils.createFloatBuffer(bevelPoints.length * 3 * curvePoints.length * (smooth ? 1 : 6)); - for (int i = 1; i < curvePoints.length - 1; ++i) { - bevelPoints = this.transformBevel(bevelPoints, curvePoints[i - 1], curvePoints[i], curvePoints[i + 1]); - bevels.add(bevelPoints); + for (int i = 1; i < curvePoints.length - 1; ++i) { + bevelPoints = this.transformBevel(bevelPoints, curvePoints[i - 1], curvePoints[i], curvePoints[i + 1]); + bevels.add(bevelPoints); + } + bevelPoints = this.transformBevel(bevelPoints, curvePoints[curvePoints.length - 2], curvePoints[curvePoints.length - 1], null); + bevels.add(bevelPoints); + + if (bevels.size() > 2) { + // changing the first and last bevel so that they are parallel to their neighbours (blender works this way) + // notice this implicates that the distances of every corresponding point in th two bevels must be identical and + // equal to the distance between the points on curve that define the bevel position + // so instead doing complicated rotations on each point we will simply properly translate each of them + + int[][] pointIndexes = new int[][] { { 0, 1 }, { curvePoints.length - 1, curvePoints.length - 2 } }; + for (int[] indexes : pointIndexes) { + float distance = curvePoints[indexes[1]].subtract(curvePoints[indexes[0]], subtractResult).length(); + Vector3f[] bevel = bevels.get(indexes[0]); + Vector3f[] nextBevel = bevels.get(indexes[1]); + for (int i = 0; i < bevel.length; ++i) { + float d = bevel[i].subtract(nextBevel[i], subtractResult).length(); + subtractResult.normalizeLocal().multLocal(distance - d); + bevel[i].addLocal(subtractResult); + } + } + } + + // apply scales to the bevels + float lengthAlongCurve = 0; + for (int i = 0; i < curvePoints.length; ++i) { + if (i > 0) { + lengthAlongCurve += curvePoints[i].subtract(curvePoints[i - 1], subtractResult).length(); + } + float taperScale = this.getTaperScale(taperObject, i == 0 ? 0 : lengthAlongCurve / curveLength); + this.applyScale(bevels.get(i), curvePoints[i], taperScale); } - bevelPoints = this.transformBevel(bevelPoints, curvePoints[curvePoints.length - 2], curvePoints[curvePoints.length - 1], null); - bevels.add(bevelPoints); - - if(bevels.size() > 2) { - //changing the first and last bevel so that they are parallel to their neighbours (blender works this way) - //notice this implicates that the distances of every corresponding point in th two bevels must be identical and - //equal to the distance between the points on curve that define the bevel position - //so instead doing complicated rotations on each point we will simply properly translate each of them - - int[][] pointIndexes = new int[][] { { 0, 1 }, { curvePoints.length - 1, curvePoints.length - 2 } }; - for(int[] indexes : pointIndexes) { - float distance = curvePoints[indexes[1]].subtract(curvePoints[indexes[0]], subtractResult).length(); - Vector3f[] bevel = bevels.get(indexes[0]); - Vector3f[] nextBevel = bevels.get(indexes[1]); - for (int i = 0; i < bevel.length; ++i) { - float d = bevel[i].subtract(nextBevel[i], subtractResult).length(); - subtractResult.normalizeLocal().multLocal(distance - d); - bevel[i].addLocal(subtractResult); - } - } - } - - //apply scales to the bevels - float lengthAlongCurve = 0; - for(int i=0;i 0) { - lengthAlongCurve += curvePoints[i].subtract(curvePoints[i - 1], subtractResult).length(); - } - float taperScale = this.getTaperScale(taperObject, i == 0 ? 0 : lengthAlongCurve / curveLength); - this.applyScale(bevels.get(i), curvePoints[i], taperScale); - } - - if(smooth) {//add everything to the buffer - for(Vector3f[] bevel : bevels) { - for(Vector3f d : bevel) { - vertexBuffers[geomIndex].put(d.x); - vertexBuffers[geomIndex].put(d.y); - vertexBuffers[geomIndex].put(d.z); - } - } - } else {//add vertices to the buffer duplicating them so that every vertex belongs only to a single triangle - for (int i = 0; i < curvePoints.length - 1; ++i) { - for (int j = 0; j < bevelPoints.length - 1; ++j) { - //first triangle - vertexBuffers[geomIndex].put(bevels.get(i)[j].x); - vertexBuffers[geomIndex].put(bevels.get(i)[j].y); - vertexBuffers[geomIndex].put(bevels.get(i)[j].z); - vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].x); - vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].y); - vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].z); - vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].x); - vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].y); - vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].z); - - //second triangle - vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].x); - vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].y); - vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].z); - vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].x); - vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].y); - vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].z); - vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].x); - vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].y); - vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].z); - } - } - } - - indexBuffers[geomIndex] = this.generateIndexes(bevelPoints.length, curvePoints.length, smooth); - normalBuffers[geomIndex] = this.generateNormals(indexBuffers[geomIndex], vertexBuffers[geomIndex], smooth); + + if (smooth) {// add everything to the buffer + for (Vector3f[] bevel : bevels) { + for (Vector3f d : bevel) { + vertexBuffers[geomIndex].put(d.x); + vertexBuffers[geomIndex].put(d.y); + vertexBuffers[geomIndex].put(d.z); + } + } + } else {// add vertices to the buffer duplicating them so that every vertex belongs only to a single triangle + for (int i = 0; i < curvePoints.length - 1; ++i) { + for (int j = 0; j < bevelPoints.length - 1; ++j) { + // first triangle + vertexBuffers[geomIndex].put(bevels.get(i)[j].x); + vertexBuffers[geomIndex].put(bevels.get(i)[j].y); + vertexBuffers[geomIndex].put(bevels.get(i)[j].z); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].x); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].y); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].z); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].x); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].y); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].z); + + // second triangle + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].x); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].y); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].z); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].x); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].y); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].z); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].x); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].y); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].z); + } + } + } + + indexBuffers[geomIndex] = this.generateIndexes(bevelPoints.length, curvePoints.length, smooth); + normalBuffers[geomIndex] = this.generateNormals(indexBuffers[geomIndex], vertexBuffers[geomIndex], smooth); } - - //creating and returning the result + + // creating and returning the result List result = new ArrayList(vertexBuffers.length); - Float oneReferenceToCurveLength = new Float(curveLength);//its important for array modifier to use one reference here + Float oneReferenceToCurveLength = new Float(curveLength);// its important for array modifier to use one reference here for (int i = 0; i < vertexBuffers.length; ++i) { Mesh mesh = new Mesh(); mesh.setBuffer(Type.Position, 3, vertexBuffers[i]); - if(indexBuffers[i].getBuffer() instanceof IntBuffer) { - mesh.setBuffer(Type.Index, 3, (IntBuffer)indexBuffers[i].getBuffer()); + if (indexBuffers[i].getBuffer() instanceof IntBuffer) { + mesh.setBuffer(Type.Index, 3, (IntBuffer) indexBuffers[i].getBuffer()); } else { - mesh.setBuffer(Type.Index, 3, (ShortBuffer)indexBuffers[i].getBuffer()); + mesh.setBuffer(Type.Index, 3, (ShortBuffer) indexBuffers[i].getBuffer()); } mesh.setBuffer(Type.Normal, 3, normalBuffers[i]); Geometry g = new Geometry("g" + i, mesh); @@ -576,211 +571,211 @@ public class CurvesHelper extends AbstractBlenderHelper { } return result; } - - /** - * the method applies scale for the given bevel points. The points table is - * being modified so expect ypur result there. - * - * @param points - * the bevel points - * @param centerPoint - * the center point of the bevel - * @param scale - * the scale to be applied - */ - private void applyScale(Vector3f[] points, Vector3f centerPoint, float scale) { - Vector3f taperScaleVector = new Vector3f(); - for (Vector3f p : points) { - taperScaleVector.set(centerPoint).subtractLocal(p).multLocal(1 - scale); - p.addLocal(taperScaleVector); - } - } - - /** - * The method generates normal buffer for the created mesh of the curve. - * - * @param indexes - * the indexes of the mesh points - * @param points - * the mesh's points - * @param smooth - * the flag indicating if the result is to be smooth or solid - * @return normals buffer for the mesh - */ - private FloatBuffer generateNormals(IndexBuffer indexes, FloatBuffer points, boolean smooth) { - Map normalMap = new TreeMap(); - Vector3f[] allVerts = BufferUtils.getVector3Array(points); - - for (int i = 0; i < indexes.size(); i += 3) { - int index1 = indexes.get(i); - int index2 = indexes.get(i + 1); - int index3 = indexes.get(i + 2); - - Vector3f n = FastMath.computeNormal(allVerts[index1], allVerts[index2], allVerts[index3]); - this.addNormal(n, normalMap, smooth, index1, index2, index3); - } - - FloatBuffer normals = BufferUtils.createFloatBuffer(normalMap.size() * 3); - for (Entry entry : normalMap.entrySet()) { - normals.put(entry.getValue().x); - normals.put(entry.getValue().y); - normals.put(entry.getValue().z); - } - return normals; - } - - /** - * The amount of faces in the final mesh is the amount of edges in the bevel - * curve (which is less by 1 than its number of vertices) multiplied by 2 - * (because each edge has two faces assigned on both sides) and multiplied - * by the amount of bevel curve repeats which is equal to the amount of - * vertices on the target curve finally we need to subtract the bevel edges - * amount 2 times because the border edges have only one face attached and - * at last multiply everything by 3 because each face needs 3 indexes to be - * described - * - * @param bevelShapeVertexCount - * amount of points in bevel shape - * @param bevelRepeats - * amount of bevel shapes along the curve - * @param smooth - * the smooth flag - * @return index buffer for the mesh - */ + + /** + * the method applies scale for the given bevel points. The points table is + * being modified so expect ypur result there. + * + * @param points + * the bevel points + * @param centerPoint + * the center point of the bevel + * @param scale + * the scale to be applied + */ + private void applyScale(Vector3f[] points, Vector3f centerPoint, float scale) { + Vector3f taperScaleVector = new Vector3f(); + for (Vector3f p : points) { + taperScaleVector.set(centerPoint).subtractLocal(p).multLocal(1 - scale); + p.addLocal(taperScaleVector); + } + } + + /** + * The method generates normal buffer for the created mesh of the curve. + * + * @param indexes + * the indexes of the mesh points + * @param points + * the mesh's points + * @param smooth + * the flag indicating if the result is to be smooth or solid + * @return normals buffer for the mesh + */ + private FloatBuffer generateNormals(IndexBuffer indexes, FloatBuffer points, boolean smooth) { + Map normalMap = new TreeMap(); + Vector3f[] allVerts = BufferUtils.getVector3Array(points); + + for (int i = 0; i < indexes.size(); i += 3) { + int index1 = indexes.get(i); + int index2 = indexes.get(i + 1); + int index3 = indexes.get(i + 2); + + Vector3f n = FastMath.computeNormal(allVerts[index1], allVerts[index2], allVerts[index3]); + this.addNormal(n, normalMap, smooth, index1, index2, index3); + } + + FloatBuffer normals = BufferUtils.createFloatBuffer(normalMap.size() * 3); + for (Entry entry : normalMap.entrySet()) { + normals.put(entry.getValue().x); + normals.put(entry.getValue().y); + normals.put(entry.getValue().z); + } + return normals; + } + + /** + * The amount of faces in the final mesh is the amount of edges in the bevel + * curve (which is less by 1 than its number of vertices) multiplied by 2 + * (because each edge has two faces assigned on both sides) and multiplied + * by the amount of bevel curve repeats which is equal to the amount of + * vertices on the target curve finally we need to subtract the bevel edges + * amount 2 times because the border edges have only one face attached and + * at last multiply everything by 3 because each face needs 3 indexes to be + * described + * + * @param bevelShapeVertexCount + * amount of points in bevel shape + * @param bevelRepeats + * amount of bevel shapes along the curve + * @param smooth + * the smooth flag + * @return index buffer for the mesh + */ private IndexBuffer generateIndexes(int bevelShapeVertexCount, int bevelRepeats, boolean smooth) { - int putIndex = 0; - if(smooth) { - int indexBufferSize = (bevelRepeats - 1) * (bevelShapeVertexCount - 1) * 6; - IndexBuffer result = IndexBuffer.createIndexBuffer(indexBufferSize, indexBufferSize); - - for (int i = 0; i < bevelRepeats - 1; ++i) { - for (int j = 0; j < bevelShapeVertexCount - 1; ++j) { - result.put(putIndex++, i * bevelShapeVertexCount + j); - result.put(putIndex++, i * bevelShapeVertexCount + j + 1); - result.put(putIndex++, (i + 1) * bevelShapeVertexCount + j); - - result.put(putIndex++, i * bevelShapeVertexCount + j + 1); - result.put(putIndex++, (i + 1) * bevelShapeVertexCount + j + 1); - result.put(putIndex++, (i + 1) * bevelShapeVertexCount + j); - } - } + int putIndex = 0; + if (smooth) { + int indexBufferSize = (bevelRepeats - 1) * (bevelShapeVertexCount - 1) * 6; + IndexBuffer result = IndexBuffer.createIndexBuffer(indexBufferSize, indexBufferSize); + + for (int i = 0; i < bevelRepeats - 1; ++i) { + for (int j = 0; j < bevelShapeVertexCount - 1; ++j) { + result.put(putIndex++, i * bevelShapeVertexCount + j); + result.put(putIndex++, i * bevelShapeVertexCount + j + 1); + result.put(putIndex++, (i + 1) * bevelShapeVertexCount + j); + + result.put(putIndex++, i * bevelShapeVertexCount + j + 1); + result.put(putIndex++, (i + 1) * bevelShapeVertexCount + j + 1); + result.put(putIndex++, (i + 1) * bevelShapeVertexCount + j); + } + } return result; - } else { - //every pair of bevel vertices belongs to two triangles - //we have the same amount of pairs as the amount of vertices in bevel - //so the amount of triangles is: bevelShapeVertexCount * 2 * (bevelRepeats - 1) - //and this gives the amount of vertices in non smooth shape as below ... - int indexBufferSize = bevelShapeVertexCount * bevelRepeats * 6;//6 = 2 * 3 where 2 is stated above and 3 is the count of vertices for each triangle - IndexBuffer result = IndexBuffer.createIndexBuffer(indexBufferSize, indexBufferSize); - for (int i = 0; i < indexBufferSize; ++i) { - result.put(putIndex++, i); - } + } else { + // every pair of bevel vertices belongs to two triangles + // we have the same amount of pairs as the amount of vertices in bevel + // so the amount of triangles is: bevelShapeVertexCount * 2 * (bevelRepeats - 1) + // and this gives the amount of vertices in non smooth shape as below ... + int indexBufferSize = bevelShapeVertexCount * bevelRepeats * 6;// 6 = 2 * 3 where 2 is stated above and 3 is the count of vertices for each triangle + IndexBuffer result = IndexBuffer.createIndexBuffer(indexBufferSize, indexBufferSize); + for (int i = 0; i < indexBufferSize; ++i) { + result.put(putIndex++, i); + } return result; - } + } } - - /** - * The method transforms the bevel along the curve. - * - * @param bevel - * the bevel to be transformed - * @param prevPos - * previous curve point - * @param currPos - * current curve point (here the center of the new bevel will be - * set) - * @param nextPos - * next curve point - * @return points of transformed bevel - */ + + /** + * The method transforms the bevel along the curve. + * + * @param bevel + * the bevel to be transformed + * @param prevPos + * previous curve point + * @param currPos + * current curve point (here the center of the new bevel will be + * set) + * @param nextPos + * next curve point + * @return points of transformed bevel + */ private Vector3f[] transformBevel(Vector3f[] bevel, Vector3f prevPos, Vector3f currPos, Vector3f nextPos) { - bevel = bevel.clone(); - - //currPos and directionVector define the line in 3D space - Vector3f directionVector = prevPos != null ? currPos.subtract(prevPos) : nextPos.subtract(currPos); - directionVector.normalizeLocal(); - - //plane is described by equation: Ax + By + Cz + D = 0 where planeNormal = [A, B, C] and D = -(Ax + By + Cz) - Vector3f planeNormal = null; - if(prevPos != null) { - planeNormal = currPos.subtract(prevPos).normalizeLocal(); - if(nextPos != null) { - planeNormal.addLocal(nextPos.subtract(currPos).normalizeLocal()).normalizeLocal(); - } - } else { - planeNormal = nextPos.subtract(currPos).normalizeLocal(); - } - float D = -planeNormal.dot(currPos);//D = -(Ax + By + Cz) - - //now we need to compute paralell cast of each bevel point on the plane, the leading line is already known - //parametric equation of a line: x = px + vx * t; y = py + vy * t; z = pz + vz * t - //where p = currPos and v = directionVector - //using x, y and z in plane equation we get value of 't' that will allow us to compute the point where plane and line cross - float temp = planeNormal.dot(directionVector); - for(int i=0;i normalMap, boolean smooth, int... indexes) { for (int index : indexes) { Vector3f n = normalMap.get(index); @@ -791,31 +786,31 @@ public class CurvesHelper extends AbstractBlenderHelper { } } } - - /** - * This method loads the taper object. - * - * @param taperStructure - * the taper structure - * @param blenderContext - * the blender context - * @return the taper object - * @throws BlenderFileException - */ + + /** + * This method loads the taper object. + * + * @param taperStructure + * the taper structure + * @param blenderContext + * the blender context + * @return the taper object + * @throws BlenderFileException + */ protected Spline loadTaperObject(Structure taperStructure, BlenderContext blenderContext) throws BlenderFileException { - //reading nurbs + // reading nurbs List nurbStructures = ((Structure) taperStructure.getFieldValue("nurb")).evaluateListBase(blenderContext); for (Structure nurb : nurbStructures) { Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt"); if (pBezierTriple.isNotNull()) { - //creating the curve object + // creating the curve object BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(blenderContext.getInputStream()), 3); List controlPoints = bezierCurve.getControlPoints(); - //removing the first and last handles + // removing the first and last handles controlPoints.remove(0); controlPoints.remove(controlPoints.size() - 1); - //return the first taper curve that has more than 3 control points + // return the first taper curve that has more than 3 control points if (controlPoints.size() > 3) { return new Spline(SplineType.Bezier, controlPoints, 0, false); } @@ -824,14 +819,14 @@ public class CurvesHelper extends AbstractBlenderHelper { return null; } - /** - * This method returns the translation of the curve. The UP axis is taken - * into account here. - * - * @param curveStructure - * the curve structure - * @return curve translation - */ + /** + * This method returns the translation of the curve. The UP axis is taken + * into account here. + * + * @param curveStructure + * the curve structure + * @return curve translation + */ @SuppressWarnings("unchecked") protected Vector3f getLoc(Structure curveStructure) { DynamicArray locArray = (DynamicArray) curveStructure.getFieldValue("loc"); @@ -841,9 +836,9 @@ public class CurvesHelper extends AbstractBlenderHelper { return new Vector3f(locArray.get(0).floatValue(), locArray.get(2).floatValue(), locArray.get(1).floatValue()); } } - + @Override public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - return true; + return true; } } \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/exceptions/BlenderFileException.java b/engine/src/blender/com/jme3/scene/plugins/blender/exceptions/BlenderFileException.java index f9b668447..fef2fb575 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/exceptions/BlenderFileException.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/exceptions/BlenderFileException.java @@ -43,13 +43,13 @@ public class BlenderFileException extends Exception { * Constructor. Creates an exception with no description. */ public BlenderFileException() { - //this constructor has no message + // this constructor has no message } /** * Constructor. Creates an exception containing the given message. * @param message - * the message describing the problem that occured + * the message describing the problem that occured */ public BlenderFileException(String message) { super(message); @@ -58,7 +58,7 @@ public class BlenderFileException extends Exception { /** * Constructor. Creates an exception that is based upon other thrown object. It contains the whole stacktrace then. * @param throwable - * an exception/error that occured + * an exception/error that occured */ public BlenderFileException(Throwable throwable) { super(throwable); @@ -67,9 +67,9 @@ public class BlenderFileException extends Exception { /** * Constructor. Creates an exception with both a message and stacktrace. * @param message - * the message describing the problem that occured + * the message describing the problem that occured * @param throwable - * an exception/error that occured + * an exception/error that occured */ public BlenderFileException(String message, Throwable throwable) { super(message, throwable); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java index 854df5e7b..871f6e84f 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/BlenderInputStream.java @@ -46,35 +46,35 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; */ public class BlenderInputStream extends InputStream { - private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName()); + private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName()); /** The default size of the blender buffer. */ - private static final int DEFAULT_BUFFER_SIZE = 1048576; //1MB + private static final int DEFAULT_BUFFER_SIZE = 1048576; // 1MB /** * Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes. */ - private int pointerSize; + private int pointerSize; /** * Type of byte ordering used; 'v' means little endian and 'V' means big endian. */ - private char endianess; + private char endianess; /** Version of Blender the file was created in; '248' means version 2.48. */ - private String versionNumber; + private String versionNumber; /** The buffer we store the read data to. */ - protected byte[] cachedBuffer; + protected byte[] cachedBuffer; /** The total size of the stored data. */ - protected int size; + protected int size; /** The current position of the read cursor. */ - protected int position; + protected int position; /** * Constructor. The input stream is stored and used to read data. * @param inputStream - * the stream we read data from + * the stream we read data from * @throws BlenderFileException - * this exception is thrown if the file header has some invalid data + * this exception is thrown if the file header has some invalid data */ public BlenderInputStream(InputStream inputStream) throws BlenderFileException { - //the size value will canche while reading the file; the available() method cannot be counted on + // the size value will canche while reading the file; the available() method cannot be counted on try { size = inputStream.available(); } catch (IOException e) { @@ -84,7 +84,7 @@ public class BlenderInputStream extends InputStream { size = BlenderInputStream.DEFAULT_BUFFER_SIZE; } - //buffered input stream is used here for much faster file reading + // buffered input stream is used here for much faster file reading BufferedInputStream bufferedInputStream; if (inputStream instanceof BufferedInputStream) { bufferedInputStream = (BufferedInputStream) inputStream; @@ -97,16 +97,16 @@ public class BlenderInputStream extends InputStream { } catch (IOException e) { throw new BlenderFileException("Problems occured while caching the file!", e); } finally { - try { - inputStream.close(); - } catch (IOException e) { - LOGGER.warning("Unable to close stream with blender file."); - } + try { + inputStream.close(); + } catch (IOException e) { + LOGGER.warning("Unable to close stream with blender file."); + } } try { this.readFileHeader(); - } catch (BlenderFileException e) {//the file might be packed, don't panic, try one more time ;) + } catch (BlenderFileException e) {// the file might be packed, don't panic, try one more time ;) this.decompressFile(); this.position = 0; this.readFileHeader(); @@ -116,22 +116,22 @@ public class BlenderInputStream extends InputStream { /** * This method reads the whole stream into a buffer. * @param inputStream - * the stream to read the file data from - * @throws IOException - * an exception is thrown when data read from the stream is invalid or there are problems with i/o - * operations + * the stream to read the file data from + * @throws IOException + * an exception is thrown when data read from the stream is invalid or there are problems with i/o + * operations */ private void readStreamToCache(InputStream inputStream) throws IOException { int data = inputStream.read(); cachedBuffer = new byte[size]; - size = 0;//this will count the actual size + size = 0;// this will count the actual size while (data != -1) { - if (size >= cachedBuffer.length) {//widen the cached array + if (size >= cachedBuffer.length) {// widen the cached array byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)]; System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length); cachedBuffer = newBuffer; } - cachedBuffer[size++] = (byte) data; + cachedBuffer[size++] = (byte) data; data = inputStream.read(); } } @@ -146,8 +146,7 @@ public class BlenderInputStream extends InputStream { gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer)); this.readStreamToCache(gis); } catch (IOException e) { - throw new IllegalStateException("IO errors occured where they should NOT! " - + "The data is already buffered at this point!", e); + throw new IllegalStateException("IO errors occured where they should NOT! " + "The data is already buffered at this point!", e); } finally { try { if (gis != null) { @@ -162,9 +161,9 @@ public class BlenderInputStream extends InputStream { /** * This method loads the header from the given stream during instance creation. * @param inputStream - * the stream we read the header from + * the stream we read the header from * @throws BlenderFileException - * this exception is thrown if the file header has some invalid data + * this exception is thrown if the file header has some invalid data */ private void readFileHeader() throws BlenderFileException { byte[] identifier = new byte[7]; @@ -213,7 +212,7 @@ public class BlenderInputStream extends InputStream { } /** - * This method reads a bytes number big enough to fill the table. + * This method reads a bytes number big enough to fill the table. * It does not throw exceptions so it is for internal use only. * @param bytes * an array to be filled with data @@ -319,7 +318,7 @@ public class BlenderInputStream extends InputStream { /** * This method sets the current position of the read cursor. * @param position - * the position of the read cursor + * the position of the read cursor */ public void setPosition(int position) { this.position = position; @@ -352,7 +351,7 @@ public class BlenderInputStream extends InputStream { /** * This method aligns cursor position forward to a given amount of bytes. * @param bytesAmount - * the byte amount to which we aligh the cursor + * the byte amount to which we aligh the cursor */ public void alignPosition(int bytesAmount) { if (bytesAmount <= 0) { @@ -365,17 +364,17 @@ public class BlenderInputStream extends InputStream { } @Override - public void close() throws IOException { - //this method is unimplemented because some loaders (ie. TGALoader) have flaws that close the stream given from the outside - //because the images can be stored directly in the blender file then this stream is properly positioned and given to the loader - //to read the image file, that is why we do not want it to be closed before the reading is done - //to properly close the stream use forceClose() method + public void close() throws IOException { + // this method is unimplemented because some loaders (ie. TGALoader) have flaws that close the stream given from the outside + // because the images can be stored directly in the blender file then this stream is properly positioned and given to the loader + // to read the image file, that is why we do not want it to be closed before the reading is done + // to properly close the stream use forceClose() method } - + /** * This method should be used to close the stream because some loaders may close the stream while reading resources from it. */ public void forceClose() { - cachedBuffer = null; + cachedBuffer = null; } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/DnaBlockData.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/DnaBlockData.java index 5957ca457..23efda54b 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/file/DnaBlockData.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/DnaBlockData.java @@ -42,39 +42,37 @@ import java.util.Map; */ public class DnaBlockData { - private static final int SDNA_ID = 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A'; //SDNA - private static final int NAME_ID = 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E'; //NAME - private static final int TYPE_ID = 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E'; //TYPE - private static final int TLEN_ID = 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N'; //TLEN - private static final int STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C'; //STRC + private static final int SDNA_ID = 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A'; // SDNA + private static final int NAME_ID = 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E'; // NAME + private static final int TYPE_ID = 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E'; // TYPE + private static final int TLEN_ID = 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N'; // TLEN + private static final int STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C'; // STRC /** Structures available inside the file. */ - private final Structure[] structures; + private final Structure[] structures; /** A map that helps finding a structure by type. */ private final Map structuresMap; /** * Constructor. Loads the block from the given stream during instance creation. * @param inputStream - * the stream we read the block from + * the stream we read the block from * @param blenderContext - * the blender context + * the blender context * @throws BlenderFileException - * this exception is throw if the blend file is invalid or somehow corrupted + * this exception is throw if the blend file is invalid or somehow corrupted */ public DnaBlockData(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException { int identifier; - //reading 'SDNA' identifier - identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 - | inputStream.readByte() << 8 | inputStream.readByte(); + // reading 'SDNA' identifier + identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); if (identifier != SDNA_ID) { throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier)); } - //reading names - identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 - | inputStream.readByte() << 8 | inputStream.readByte(); + // reading names + identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); if (identifier != NAME_ID) { throw new BlenderFileException("Invalid identifier! '" + this.toString(NAME_ID) + "' expected and found: " + this.toString(identifier)); } @@ -87,10 +85,9 @@ public class DnaBlockData { names[i] = inputStream.readString(); } - //reding types + // reding types inputStream.alignPosition(4); - identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 - | inputStream.readByte() << 8 | inputStream.readByte(); + identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); if (identifier != TYPE_ID) { throw new BlenderFileException("Invalid identifier! '" + this.toString(TYPE_ID) + "' expected and found: " + this.toString(identifier)); } @@ -103,22 +100,20 @@ public class DnaBlockData { types[i] = inputStream.readString(); } - //reading lengths + // reading lengths inputStream.alignPosition(4); - identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 - | inputStream.readByte() << 8 | inputStream.readByte(); + identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); if (identifier != TLEN_ID) { throw new BlenderFileException("Invalid identifier! '" + this.toString(TLEN_ID) + "' expected and found: " + this.toString(identifier)); } - int[] lengths = new int[amount];//theamount is the same as int types + int[] lengths = new int[amount];// theamount is the same as int types for (int i = 0; i < amount; ++i) { lengths[i] = inputStream.readShort(); } - //reading structures + // reading structures inputStream.alignPosition(4); - identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 - | inputStream.readByte() << 8 | inputStream.readByte(); + identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); if (identifier != STRC_ID) { throw new BlenderFileException("Invalid identifier! '" + this.toString(STRC_ID) + "' expected and found: " + this.toString(identifier)); } @@ -148,7 +143,7 @@ public class DnaBlockData { /** * This method returns the structure of the given index. * @param index - * the index of the structure + * the index of the structure * @return the structure of the given index */ public Structure getStructure(int index) { @@ -162,7 +157,7 @@ public class DnaBlockData { /** * This method returns a structure of the given name. If the name does not exists then null is returned. * @param name - * the name of the structure + * the name of the structure * @return the required structure or null if the given name is inapropriate */ public Structure getStructure(String name) { @@ -176,7 +171,7 @@ public class DnaBlockData { /** * This method indicates if the structure of the given name exists. * @param name - * the name of the structure + * the name of the structure * @return true if the structure exists and false otherwise */ public boolean hasStructure(String name) { @@ -186,7 +181,7 @@ public class DnaBlockData { /** * This method converts the given identifier code to string. * @param code - * the code taht is to be converted + * the code taht is to be converted * @return the string value of the identifier */ private String toString(int code) { diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/DynamicArray.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/DynamicArray.java index 23e4d18f0..1cda607d3 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/file/DynamicArray.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/DynamicArray.java @@ -37,12 +37,12 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; * An array that can be dynamically modified/ * @author Marcin Roguski * @param - * the type of stored data in the array + * the type of stored data in the array */ public class DynamicArray implements Cloneable { /** An array object that holds the required data. */ - private T[] array; + private T[] array; /** * This table holds the sizes of dimetions of the dynamic table. It's length specifies the table dimension or a * pointer level. For example: if tableSizes.length == 3 then it either specifies a dynamic table of fixed lengths: @@ -53,9 +53,9 @@ public class DynamicArray implements Cloneable { /** * Constructor. Builds an empty array of the specified sizes. * @param tableSizes - * the sizes of the table + * the sizes of the table * @throws BlenderFileException - * an exception is thrown if one of the sizes is not a positive number + * an exception is thrown if one of the sizes is not a positive number */ @SuppressWarnings("unchecked") public DynamicArray(int[] tableSizes) throws BlenderFileException { @@ -73,9 +73,9 @@ public class DynamicArray implements Cloneable { /** * Constructor. Builds an empty array of the specified sizes. * @param tableSizes - * the sizes of the table + * the sizes of the table * @throws BlenderFileException - * an exception is thrown if one of the sizes is not a positive number + * an exception is thrown if one of the sizes is not a positive number */ public DynamicArray(int[] tableSizes, T[] data) throws BlenderFileException { this.tableSizes = tableSizes; @@ -101,7 +101,7 @@ public class DynamicArray implements Cloneable { * This method returns a value on the specified position. The dimension of the table is not taken into * consideration. * @param position - * the position of the data + * the position of the data * @return required data */ public T get(int position) { @@ -112,7 +112,7 @@ public class DynamicArray implements Cloneable { * This method returns a value on the specified position in multidimensional array. Be careful not to exceed the * table boundaries. Check the table's dimension first. * @param position - * the position of the data indices of data position + * the position of the data indices of data position * @return required data required data */ public T get(int... position) { @@ -138,8 +138,8 @@ public class DynamicArray implements Cloneable { @Override public String toString() { StringBuilder result = new StringBuilder(); - if (array instanceof Character[]) {//in case of character array we convert it to String - for (int i = 0; i < array.length && (Character) array[i] != '\0'; ++i) {//strings are terminater with '0' + if (array instanceof Character[]) {// in case of character array we convert it to String + for (int i = 0; i < array.length && (Character) array[i] != '\0'; ++i) {// strings are terminater with '0' result.append(array[i]); } } else { diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/Field.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/Field.java index 06197bef7..8b085c689 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/file/Field.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/Field.java @@ -11,38 +11,38 @@ import java.util.List; * another structure. * @author Marcin Roguski */ -/*package*/ +/* package */ class Field implements Cloneable { private static final int NAME_LENGTH = 24; private static final int TYPE_LENGTH = 16; /** The blender context. */ - public BlenderContext blenderContext; + public BlenderContext blenderContext; /** The type of the field. */ - public String type; + public String type; /** The name of the field. */ - public String name; + public String name; /** The value of the field. Filled during data reading. */ - public Object value; + public Object value; /** This variable indicates the level of the pointer. */ - public int pointerLevel; + public int pointerLevel; /** * This variable determines the sizes of the array. If the value is null the n the field is not an array. */ - public int[] tableSizes; + public int[] tableSizes; /** This variable indicates if the field is a function pointer. */ - public boolean function; + public boolean function; /** * Constructor. Saves the field data and parses its name. * @param name - * the name of the field + * the name of the field * @param type - * the type of the field + * the type of the field * @param blenderContext - * the blender context + * the blender context * @throws BlenderFileException - * this exception is thrown if the names contain errors + * this exception is thrown if the names contain errors */ public Field(String name, String type, BlenderContext blenderContext) throws BlenderFileException { this.type = type; @@ -54,7 +54,7 @@ class Field implements Cloneable { * Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we * have a clead empty copy of the filed to fill with data. * @param field - * the object that we copy + * the object that we copy */ private Field(Field field) { type = field.type; @@ -75,9 +75,9 @@ class Field implements Cloneable { /** * This method fills the field wth data read from the input stream. * @param blenderInputStream - * the stream we read data from + * the stream we read data from * @throws BlenderFileException - * an exception is thrown when the blend file is somehow invalid or corrupted + * an exception is thrown when the blend file is somehow invalid or corrupted */ public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException { int dataToRead = 1; @@ -107,9 +107,9 @@ class Field implements Cloneable { } break; case CHARACTER: - //character is also stored as a number, because sometimes the new blender version uses - //other number type instead of character as a field type - //and characters are very often used as byte number stores instead of real chars + // character is also stored as a number, because sometimes the new blender version uses + // other number type instead of character as a field type + // and characters are very often used as byte number stores instead of real chars if (dataToRead == 1) { value = Byte.valueOf((byte) blenderInputStream.readByte()); } else { @@ -200,28 +200,28 @@ class Field implements Cloneable { /** * This method parses the field name to determine how the field should be used. * @param nameBuilder - * the name of the field (given as StringBuilder) + * the name of the field (given as StringBuilder) * @throws BlenderFileException - * this exception is thrown if the names contain errors + * this exception is thrown if the names contain errors */ private void parseField(StringBuilder nameBuilder) throws BlenderFileException { this.removeWhitespaces(nameBuilder); - //veryfying if the name is a pointer + // veryfying if the name is a pointer int pointerIndex = nameBuilder.indexOf("*"); while (pointerIndex >= 0) { ++pointerLevel; nameBuilder.deleteCharAt(pointerIndex); pointerIndex = nameBuilder.indexOf("*"); } - //veryfying if the name is a function pointer + // veryfying if the name is a function pointer if (nameBuilder.indexOf("(") >= 0) { function = true; this.removeCharacter(nameBuilder, '('); this.removeCharacter(nameBuilder, ')'); } else { - //veryfying if the name is a table + // veryfying if the name is a table int tableStartIndex = 0; - List lengths = new ArrayList(3);//3 dimensions will be enough in most cases + List lengths = new ArrayList(3);// 3 dimensions will be enough in most cases do { tableStartIndex = nameBuilder.indexOf("["); if (tableStartIndex > 0) { @@ -250,9 +250,9 @@ class Field implements Cloneable { /** * This method removes the required character from the text. * @param text - * the text we remove characters from + * the text we remove characters from * @param toRemove - * the character to be removed + * the character to be removed */ private void removeCharacter(StringBuilder text, char toRemove) { for (int i = 0; i < text.length(); ++i) { @@ -266,7 +266,7 @@ class Field implements Cloneable { /** * This method removes all whitespaces from the text. * @param text - * the text we remove whitespaces from + * the text we remove whitespaces from */ private void removeWhitespaces(StringBuilder text) { for (int i = 0; i < text.length(); ++i) { @@ -276,13 +276,13 @@ class Field implements Cloneable { } } } - + /** * This method builds the full name of the field (with function, pointer and table indications). * @return the full name of the field */ public String getFullName() { - StringBuilder result = new StringBuilder(); + StringBuilder result = new StringBuilder(); if (function) { result.append('('); } @@ -306,10 +306,10 @@ class Field implements Cloneable { StringBuilder result = new StringBuilder(); result.append(this.getFullName()); - //insert appropriate amount of spaces to format the output corrently + // insert appropriate amount of spaces to format the output corrently int nameLength = result.length(); - result.append(' ');//at least one space is a must - for (int i = 1; i < NAME_LENGTH - nameLength; ++i) {//we start from i=1 because one space is already added + result.append(' ');// at least one space is a must + for (int i = 1; i < NAME_LENGTH - nameLength; ++i) {// we start from i=1 because one space is already added result.append(' '); } result.append(type); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/FileBlockHeader.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/FileBlockHeader.java index b7fb81578..079da5001 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/file/FileBlockHeader.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/FileBlockHeader.java @@ -41,52 +41,51 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; */ public class FileBlockHeader { - public static final int BLOCK_TE00 = 'T' << 24 | 'E' << 16; //TE00 - public static final int BLOCK_ME00 = 'M' << 24 | 'E' << 16; //ME00 - public static final int BLOCK_SR00 = 'S' << 24 | 'R' << 16; //SR00 - public static final int BLOCK_CA00 = 'C' << 24 | 'A' << 16; //CA00 - public static final int BLOCK_LA00 = 'L' << 24 | 'A' << 16; //LA00 - public static final int BLOCK_OB00 = 'O' << 24 | 'B' << 16; //OB00 - public static final int BLOCK_MA00 = 'M' << 24 | 'A' << 16; //MA00 - public static final int BLOCK_SC00 = 'S' << 24 | 'C' << 16; //SC00 - public static final int BLOCK_WO00 = 'W' << 24 | 'O' << 16; //WO00 - public static final int BLOCK_TX00 = 'T' << 24 | 'X' << 16; //TX00 - public static final int BLOCK_IP00 = 'I' << 24 | 'P' << 16; //IP00 - public static final int BLOCK_AC00 = 'A' << 24 | 'C' << 16; //AC00 - public static final int BLOCK_GLOB = 'G' << 24 | 'L' << 16 | 'O' << 8 | 'B'; //GLOB - public static final int BLOCK_REND = 'R' << 24 | 'E' << 16 | 'N' << 8 | 'D'; //REND - public static final int BLOCK_DATA = 'D' << 24 | 'A' << 16 | 'T' << 8 | 'A'; //DATA - public static final int BLOCK_DNA1 = 'D' << 24 | 'N' << 16 | 'A' << 8 | '1'; //DNA1 - public static final int BLOCK_ENDB = 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B'; //ENDB + public static final int BLOCK_TE00 = 'T' << 24 | 'E' << 16; // TE00 + public static final int BLOCK_ME00 = 'M' << 24 | 'E' << 16; // ME00 + public static final int BLOCK_SR00 = 'S' << 24 | 'R' << 16; // SR00 + public static final int BLOCK_CA00 = 'C' << 24 | 'A' << 16; // CA00 + public static final int BLOCK_LA00 = 'L' << 24 | 'A' << 16; // LA00 + public static final int BLOCK_OB00 = 'O' << 24 | 'B' << 16; // OB00 + public static final int BLOCK_MA00 = 'M' << 24 | 'A' << 16; // MA00 + public static final int BLOCK_SC00 = 'S' << 24 | 'C' << 16; // SC00 + public static final int BLOCK_WO00 = 'W' << 24 | 'O' << 16; // WO00 + public static final int BLOCK_TX00 = 'T' << 24 | 'X' << 16; // TX00 + public static final int BLOCK_IP00 = 'I' << 24 | 'P' << 16; // IP00 + public static final int BLOCK_AC00 = 'A' << 24 | 'C' << 16; // AC00 + public static final int BLOCK_GLOB = 'G' << 24 | 'L' << 16 | 'O' << 8 | 'B'; // GLOB + public static final int BLOCK_REND = 'R' << 24 | 'E' << 16 | 'N' << 8 | 'D'; // REND + public static final int BLOCK_DATA = 'D' << 24 | 'A' << 16 | 'T' << 8 | 'A'; // DATA + public static final int BLOCK_DNA1 = 'D' << 24 | 'N' << 16 | 'A' << 8 | '1'; // DNA1 + public static final int BLOCK_ENDB = 'E' << 24 | 'N' << 16 | 'D' << 8 | 'B'; // ENDB /** Identifier of the file-block [4 bytes]. */ - private int code; + private int code; /** Total length of the data after the file-block-header [4 bytes]. */ - private int size; + private int size; /** * Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer * size)]. */ - private long oldMemoryAddress; + private long oldMemoryAddress; /** Index of the SDNA structure [4 bytes]. */ - private int sdnaIndex; + private int sdnaIndex; /** Number of structure located in this file-block [4 bytes]. */ - private int count; + private int count; /** Start position of the block's data in the stream. */ - private int blockPosition; + private int blockPosition; /** * Constructor. Loads the block header from the given stream during instance creation. * @param inputStream - * the stream we read the block header from + * the stream we read the block header from * @param blenderContext - * the blender context + * the blender context * @throws BlenderFileException - * this exception is thrown when the pointer size is neither 4 nor 8 + * this exception is thrown when the pointer size is neither 4 nor 8 */ public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException { inputStream.alignPosition(4); - code = inputStream.readByte() << 24 | inputStream.readByte() << 16 - | inputStream.readByte() << 8 | inputStream.readByte(); + code = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); size = inputStream.readInt(); oldMemoryAddress = inputStream.readPointer(); sdnaIndex = inputStream.readInt(); @@ -103,7 +102,7 @@ public class FileBlockHeader { /** * This method returns the structure described by the header filled with appropriate data. * @param blenderContext - * the blender context + * the blender context * @return structure filled with data * @throws BlenderFileException */ @@ -186,7 +185,7 @@ public class FileBlockHeader { /** * This method transforms the coded bloch id into a string value. * @param code - * the id of the block + * the id of the block * @return the string value of the block id */ protected String codeToString(int code) { diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/Pointer.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/Pointer.java index ec5c25cc2..a7b42a3c2 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/file/Pointer.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/Pointer.java @@ -45,20 +45,20 @@ public class Pointer { /** The blender context. */ private BlenderContext blenderContext; /** The level of the pointer. */ - private int pointerLevel; + private int pointerLevel; /** The address in file it points to. */ - private long oldMemoryAddress; + private long oldMemoryAddress; /** This variable indicates if the field is a function pointer. */ - public boolean function; + public boolean function; /** * Constructr. Stores the basic data about the pointer. * @param pointerLevel - * the level of the pointer + * the level of the pointer * @param function - * this variable indicates if the field is a function pointer + * this variable indicates if the field is a function pointer * @param blenderContext - * the repository f data; used in fetching the value that the pointer points + * the repository f data; used in fetching the value that the pointer points */ public Pointer(int pointerLevel, boolean function, BlenderContext blenderContext) { this.pointerLevel = pointerLevel; @@ -70,7 +70,7 @@ public class Pointer { * This method fills the pointer with its address value (it doesn't get the actual data yet. Use the 'fetch' method * for this. * @param inputStream - * the stream we read the pointer value from + * the stream we read the pointer value from */ public void fill(BlenderInputStream inputStream) { oldMemoryAddress = inputStream.readPointer(); @@ -79,10 +79,10 @@ public class Pointer { /** * This method fetches the data stored under the given address. * @param inputStream - * the stream we read data from + * the stream we read data from * @return the data read from the file * @throws BlenderFileException - * this exception is thrown when the blend file structure is somehow invalid or corrupted + * this exception is thrown when the blend file structure is somehow invalid or corrupted */ public List fetchData(BlenderInputStream inputStream) throws BlenderFileException { if (oldMemoryAddress == 0) { @@ -90,9 +90,8 @@ public class Pointer { } List structures = null; FileBlockHeader dataFileBlock = blenderContext.getFileBlock(oldMemoryAddress); - if(dataFileBlock == null) { - throw new BlenderFileException("No data stored for address: " +oldMemoryAddress + - ". Rarely blender makes mistakes when storing data. Try resaving the model after making minor changes. This usually helps."); + if (dataFileBlock == null) { + throw new BlenderFileException("No data stored for address: " + oldMemoryAddress + ". Rarely blender makes mistakes when storing data. Try resaving the model after making minor changes. This usually helps."); } if (pointerLevel > 1) { int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount(); @@ -108,12 +107,12 @@ public class Pointer { structures.addAll(p.fetchData(inputStream)); } } else { - //it is necessary to put null's if the pointer is null, ie. in materials array that is attached to the mesh, the index - //of the material is important, that is why we need null's to indicate that some materials' slots are empty - if(structures == null) { - structures = new ArrayList(); - } - structures.add(null); + // it is necessary to put null's if the pointer is null, ie. in materials array that is attached to the mesh, the index + // of the material is important, that is why we need null's to indicate that some materials' slots are empty + if (structures == null) { + structures = new ArrayList(); + } + structures.add(null); } } } else { @@ -144,7 +143,7 @@ public class Pointer { public boolean isNull() { return oldMemoryAddress == 0; } - + /** * This method indicates if this is a null-pointer or not. * @return true if the pointer is not null and false otherwise diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/file/Structure.java b/engine/src/blender/com/jme3/scene/plugins/blender/file/Structure.java index 005cf6072..bc4f11600 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/file/Structure.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/file/Structure.java @@ -49,20 +49,20 @@ public class Structure implements Cloneable { /** The address of the block that fills the structure. */ private transient Long oldMemoryAddress; /** The type of the structure. */ - private String type; + private String type; /** * The fields of the structure. Each field consists of a pair: name-type. */ - private Field[] fields; + private Field[] fields; /** * Constructor that copies the data of the structure. * @param structure - * the structure to copy. + * the structure to copy. * @param blenderContext - * the blender context of the structure + * the blender context of the structure * @throws CloneNotSupportedException - * this exception should never be thrown + * this exception should never be thrown */ private Structure(Structure structure, BlenderContext blenderContext) throws CloneNotSupportedException { type = structure.type; @@ -77,15 +77,15 @@ public class Structure implements Cloneable { /** * Constructor. Loads the structure from the given stream during instance creation. * @param inputStream - * the stream we read the structure from + * the stream we read the structure from * @param names - * the names from which the name of structure and its fields will be taken + * the names from which the name of structure and its fields will be taken * @param types - * the names of types for the structure + * the names of types for the structure * @param blenderContext - * the blender context + * the blender context * @throws BlenderFileException - * this exception occurs if the amount of fields, defined in the file, is negative + * this exception occurs if the amount of fields, defined in the file, is negative */ public Structure(BlenderInputStream inputStream, String[] names, String[] types, BlenderContext blenderContext) throws BlenderFileException { int nameIndex = inputStream.readShort(); @@ -109,10 +109,10 @@ public class Structure implements Cloneable { /** * This method fills the structure with data. * @param inputStream - * the stream we read data from, its read cursor should be placed at the start position of the data for the - * structure + * the stream we read data from, its read cursor should be placed at the start position of the data for the + * structure * @throws BlenderFileException - * an exception is thrown when the blend file is somehow invalid or corrupted + * an exception is thrown when the blend file is somehow invalid or corrupted */ public void fill(BlenderInputStream inputStream) throws BlenderFileException { int position = inputStream.getPosition(); @@ -127,7 +127,7 @@ public class Structure implements Cloneable { /** * This method returns the value of the filed with a given name. * @param fieldName - * the name of the field + * the name of the field * @return the value of the field or null if no field with a given name is found */ public Object getFieldValue(String fieldName) { @@ -143,7 +143,7 @@ public class Structure implements Cloneable { * This method returns the value of the filed with a given name. The structure is considered to have flat fields * only (no substructures). * @param fieldName - * the name of the field + * the name of the field * @return the value of the field or null if no field with a given name is found */ public Object getFlatFieldValue(String fieldName) { @@ -153,7 +153,7 @@ public class Structure implements Cloneable { return value; } else if (value instanceof Structure) { value = ((Structure) value).getFlatFieldValue(fieldName); - if (value != null) {//we can compare references here, since we use one static object as a NULL field value + if (value != null) {// we can compare references here, since we use one static object as a NULL field value return value; } } @@ -165,12 +165,12 @@ public class Structure implements Cloneable { * This methos should be used on structures that are of a 'ListBase' type. It creates a List of structures that are * held by this structure within the blend file. * @param blenderContext - * the blender context + * the blender context * @return a list of filled structures * @throws BlenderFileException - * this exception is thrown when the blend file structure is somehow invalid or corrupted + * this exception is thrown when the blend file structure is somehow invalid or corrupted * @throws IllegalArgumentException - * this exception is thrown if the type of the structure is not 'ListBase' + * this exception is thrown if the type of the structure is not 'ListBase' */ public List evaluateListBase(BlenderContext blenderContext) throws BlenderFileException { if (!"ListBase".equals(this.type)) { @@ -209,17 +209,17 @@ public class Structure implements Cloneable { /** * This method returns the field name of the given index. * @param fieldIndex - * the index of the field + * the index of the field * @return the field name of the given index */ public String getFieldName(int fieldIndex) { return fields[fieldIndex].name; } - + /** * This method returns the full field name of the given index. * @param fieldIndex - * the index of the field + * the index of the field * @return the full field name of the given index */ public String getFieldFullName(int fieldIndex) { @@ -229,7 +229,7 @@ public class Structure implements Cloneable { /** * This method returns the field type of the given index. * @param fieldIndex - * the index of the field + * the index of the field * @return the field type of the given index */ public String getFieldType(int fieldIndex) { @@ -248,20 +248,20 @@ public class Structure implements Cloneable { return oldMemoryAddress; } -/** - * This method returns the name of the structure. If the structure has an ID field then the name is returned. - * Otherwise the name does not exists and the method returns null. - * @return the name of the structure read from the ID field or null - */ - public String getName() { - Object fieldValue = this.getFieldValue("ID"); - if(fieldValue instanceof Structure) { - Structure id = (Structure)fieldValue; - return id == null ? null : id.getFieldValue("name").toString().substring(2);//blender adds 2-charactes as a name prefix - } - return null; - } - + /** + * This method returns the name of the structure. If the structure has an ID field then the name is returned. + * Otherwise the name does not exists and the method returns null. + * @return the name of the structure read from the ID field or null + */ + public String getName() { + Object fieldValue = this.getFieldValue("ID"); + if (fieldValue instanceof Structure) { + Structure id = (Structure) fieldValue; + return id == null ? null : id.getFieldValue("name").toString().substring(2);// blender adds 2-charactes as a name prefix + } + return null; + } + @Override public String toString() { StringBuilder result = new StringBuilder("struct ").append(type).append(" {\n"); @@ -280,7 +280,7 @@ public class Structure implements Cloneable { * This enum enumerates all known data types that can be found in the blend file. * @author Marcin Roguski */ - /*package*/ + /* package */ static enum DataType { CHARACTER, SHORT, INTEGER, LONG, FLOAT, DOUBLE, VOID, STRUCTURE, POINTER; @@ -305,12 +305,12 @@ public class Structure implements Cloneable { * This method returns the data type that is appropriate to the given type name. WARNING! The type recognition * is case sensitive! * @param type - * the type name of the data + * the type name of the data * @param blenderContext - * the blender context + * the blender context * @return appropriate enum value to the given type name * @throws BlenderFileException - * this exception is thrown if the given type name does not exist in the blend file + * this exception is thrown if the given type name does not exist in the blend file */ public static DataType getDataType(String type, BlenderContext blenderContext) throws BlenderFileException { DataType result = PRIMARY_TYPES.get(type); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/lights/LightHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/lights/LightHelper.java index d2f38c058..9d2df52ef 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/lights/LightHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/lights/LightHelper.java @@ -59,49 +59,49 @@ public class LightHelper extends AbstractBlenderHelper { * This constructor parses the given blender version and stores the result. Some functionalities may differ in * different blender versions. * @param blenderVersion - * the version read from the blend file + * the version read from the blend file * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not + * a variable that indicates if the Y asxis is the UP axis or not */ public LightHelper(String blenderVersion, boolean fixUpAxis) { super(blenderVersion, fixUpAxis); } public LightNode toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + LightNode result = (LightNode) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); if (result != null) { return result; } Light light = null; int type = ((Number) structure.getFieldValue("type")).intValue(); switch (type) { - case 0://Lamp - light = new PointLight(); + case 0:// Lamp + light = new PointLight(); float distance = ((Number) structure.getFieldValue("dist")).floatValue(); ((PointLight) light).setRadius(distance); break; - case 1://Sun + case 1:// Sun LOGGER.log(Level.WARNING, "'Sun' lamp is not supported in jMonkeyEngine."); break; - case 2://Spot - light = new SpotLight(); - //range - ((SpotLight)light).setSpotRange(((Number) structure.getFieldValue("dist")).floatValue()); - //outer angle - float outerAngle = ((Number) structure.getFieldValue("spotsize")).floatValue()*FastMath.DEG_TO_RAD * 0.5f; - ((SpotLight)light).setSpotOuterAngle(outerAngle); - - //inner angle - float spotblend = ((Number) structure.getFieldValue("spotblend")).floatValue(); + case 2:// Spot + light = new SpotLight(); + // range + ((SpotLight) light).setSpotRange(((Number) structure.getFieldValue("dist")).floatValue()); + // outer angle + float outerAngle = ((Number) structure.getFieldValue("spotsize")).floatValue() * FastMath.DEG_TO_RAD * 0.5f; + ((SpotLight) light).setSpotOuterAngle(outerAngle); + + // inner angle + float spotblend = ((Number) structure.getFieldValue("spotblend")).floatValue(); spotblend = FastMath.clamp(spotblend, 0, 1); float innerAngle = outerAngle * (1 - spotblend); - ((SpotLight)light).setSpotInnerAngle(innerAngle); + ((SpotLight) light).setSpotInnerAngle(innerAngle); break; - case 3://Hemi + case 3:// Hemi LOGGER.log(Level.WARNING, "'Hemi' lamp is not supported in jMonkeyEngine."); break; - case 4://Area - light = new DirectionalLight(); + case 4:// Area + light = new DirectionalLight(); break; default: throw new BlenderFileException("Unknown light source type: " + type); @@ -115,9 +115,9 @@ public class LightHelper extends AbstractBlenderHelper { } return result; } - + @Override public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0; + return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0; } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/materials/IAlphaMask.java b/engine/src/blender/com/jme3/scene/plugins/blender/materials/IAlphaMask.java index 45e6f3585..7fddd3d41 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/materials/IAlphaMask.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/materials/IAlphaMask.java @@ -4,23 +4,23 @@ package com.jme3.scene.plugins.blender.materials; * An interface used in calculating alpha mask during particles' texture calculations. * @author Marcin Roguski (Kaelthas) */ -/*package*/ interface IAlphaMask { - /** - * This method sets the size of the texture's image. - * @param width - * the width of the image - * @param height - * the height of the image - */ - void setImageSize(int width, int height); +/* package */interface IAlphaMask { + /** + * This method sets the size of the texture's image. + * @param width + * the width of the image + * @param height + * the height of the image + */ + void setImageSize(int width, int height); - /** - * This method returns the alpha value for the specified texture position. - * @param x - * the X coordinate of the texture position - * @param y - * the Y coordinate of the texture position - * @return the alpha value for the specified texture position - */ - byte getAlpha(float x, float y); + /** + * This method returns the alpha value for the specified texture position. + * @param x + * the X coordinate of the texture position + * @param y + * the Y coordinate of the texture position + * @return the alpha value for the specified texture position + */ + byte getAlpha(float x, float y); } \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java index e9745a9ce..5e0ee83a0 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialContext.java @@ -36,378 +36,377 @@ import com.jme3.util.BufferUtils; * @author Marcin Roguski (Kaelthas) */ public final class MaterialContext { - private static final Logger LOGGER = Logger.getLogger(MaterialContext.class.getName()); - - //texture mapping types - public static final int MTEX_COL = 0x01; - public static final int MTEX_NOR = 0x02; - public static final int MTEX_SPEC = 0x04; - public static final int MTEX_EMIT = 0x40; - public static final int MTEX_ALPHA = 0x80; - - /* package */final String name; - /* package */final Map loadedTextures; - - /* package */final ColorRGBA diffuseColor; - /* package */final DiffuseShader diffuseShader; - /* package */final SpecularShader specularShader; - /* package */final ColorRGBA specularColor; - /* package */final ColorRGBA ambientColor; - /* package */final float shininess; - /* package */final boolean shadeless; - /* package */final boolean vertexColor; - /* package */final boolean transparent; - /* package */final boolean vTangent; - /* package */FaceCullMode faceCullMode; - - @SuppressWarnings("unchecked") - /* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - name = structure.getName(); - - int mode = ((Number) structure.getFieldValue("mode")).intValue(); - shadeless = (mode & 0x4) != 0; - vertexColor = (mode & 0x80) != 0; - vTangent = (mode & 0x4000000) != 0; // NOTE: Requires tangents - - int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue(); - diffuseShader = DiffuseShader.values()[diff_shader]; - - if(this.shadeless) { + private static final Logger LOGGER = Logger.getLogger(MaterialContext.class.getName()); + + // texture mapping types + public static final int MTEX_COL = 0x01; + public static final int MTEX_NOR = 0x02; + public static final int MTEX_SPEC = 0x04; + public static final int MTEX_EMIT = 0x40; + public static final int MTEX_ALPHA = 0x80; + + /* package */final String name; + /* package */final Map loadedTextures; + + /* package */final ColorRGBA diffuseColor; + /* package */final DiffuseShader diffuseShader; + /* package */final SpecularShader specularShader; + /* package */final ColorRGBA specularColor; + /* package */final ColorRGBA ambientColor; + /* package */final float shininess; + /* package */final boolean shadeless; + /* package */final boolean vertexColor; + /* package */final boolean transparent; + /* package */final boolean vTangent; + /* package */FaceCullMode faceCullMode; + + @SuppressWarnings("unchecked") + /* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + name = structure.getName(); + + int mode = ((Number) structure.getFieldValue("mode")).intValue(); + shadeless = (mode & 0x4) != 0; + vertexColor = (mode & 0x80) != 0; + vTangent = (mode & 0x4000000) != 0; // NOTE: Requires tangents + + int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue(); + diffuseShader = DiffuseShader.values()[diff_shader]; + + if (this.shadeless) { float r = ((Number) structure.getFieldValue("r")).floatValue(); float g = ((Number) structure.getFieldValue("g")).floatValue(); float b = ((Number) structure.getFieldValue("b")).floatValue(); float alpha = ((Number) structure.getFieldValue("alpha")).floatValue(); - diffuseColor = new ColorRGBA(r, g, b, alpha); - specularShader = null; - specularColor = ambientColor = null; - shininess = 0.0f; - } else { - diffuseColor = this.readDiffuseColor(structure, diffuseShader); - - int spec_shader = ((Number) structure.getFieldValue("spec_shader")).intValue(); - specularShader = SpecularShader.values()[spec_shader]; - specularColor = this.readSpecularColor(structure, specularShader); - - float r = ((Number) structure.getFieldValue("ambr")).floatValue(); - float g = ((Number) structure.getFieldValue("ambg")).floatValue(); - float b = ((Number) structure.getFieldValue("ambb")).floatValue(); - float alpha = ((Number) structure.getFieldValue("alpha")).floatValue(); - ambientColor = new ColorRGBA(r, g, b, alpha); - - float shininess = ((Number) structure.getFieldValue("emit")).floatValue(); - this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS; - } - - DynamicArray mtexsArray = (DynamicArray) structure.getFieldValue("mtex"); - int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue(); - List texturesList = new ArrayList(); - for (int i = 0; i < mtexsArray.getTotalSize(); ++i) { - Pointer p = mtexsArray.get(i); - if (p.isNotNull() && (separatedTextures & 1 << i) == 0) { - TextureData textureData = new TextureData(); - textureData.mtex = p.fetchData(blenderContext.getInputStream()).get(0); - textureData.uvCoordinatesType = ((Number) textureData.mtex.getFieldValue("texco")).intValue(); - textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue(); - - Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex"); - if (pTex.isNotNull()) { - Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0); - textureData.textureStructure = tex; - texturesList.add(textureData); - } - } - } - - //loading the textures and merging them - Map> textureDataMap = this.sortAndFilterTextures(texturesList); - loadedTextures = new HashMap(); - float[] diffuseColorArray = new float[] {diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a}; - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - for(Entry> entry : textureDataMap.entrySet()) { - if(entry.getValue().size()>0) { - CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue()); - for(TextureData textureData : entry.getValue()) { - int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); - boolean negateTexture = (texflag & 0x04) != 0; - Texture texture = textureHelper.getTexture(textureData.textureStructure, textureData.mtex, blenderContext); - if(texture != null) { - int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue(); - float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), - ((Number) textureData.mtex.getFieldValue("g")).floatValue(), - ((Number) textureData.mtex.getFieldValue("b")).floatValue() }; - float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue(); - TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), - texflag, negateTexture, blendType, diffuseColorArray, color, colfac); - combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, textureData.textureStructure, blenderContext); - } - } - if(combinedTexture.getTexturesCount() > 0) { - loadedTextures.put(entry.getKey(), combinedTexture); - } - } - } - - //veryfying if the transparency is present - //(in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when - //it is not required - boolean transparent = false; - if(diffuseColor != null) { - transparent = diffuseColor.a < 1.0f; - if(textureDataMap.size() > 0) {//texutre covers the material color - diffuseColor.set(1, 1, 1, 1); - } - } - if(specularColor != null) { - transparent = transparent || specularColor.a < 1.0f; - } - if(ambientColor != null) { - transparent = transparent || ambientColor.a < 1.0f; - } - this.transparent = transparent; - } - - public void applyMaterial(Geometry geometry, Long geometriesOMA, List userDefinedUVCoordinates, BlenderContext blenderContext) { - Material material = null; - if (shadeless) { - material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); - + diffuseColor = new ColorRGBA(r, g, b, alpha); + specularShader = null; + specularColor = ambientColor = null; + shininess = 0.0f; + } else { + diffuseColor = this.readDiffuseColor(structure, diffuseShader); + + int spec_shader = ((Number) structure.getFieldValue("spec_shader")).intValue(); + specularShader = SpecularShader.values()[spec_shader]; + specularColor = this.readSpecularColor(structure, specularShader); + + float r = ((Number) structure.getFieldValue("ambr")).floatValue(); + float g = ((Number) structure.getFieldValue("ambg")).floatValue(); + float b = ((Number) structure.getFieldValue("ambb")).floatValue(); + float alpha = ((Number) structure.getFieldValue("alpha")).floatValue(); + ambientColor = new ColorRGBA(r, g, b, alpha); + + float shininess = ((Number) structure.getFieldValue("emit")).floatValue(); + this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS; + } + + DynamicArray mtexsArray = (DynamicArray) structure.getFieldValue("mtex"); + int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue(); + List texturesList = new ArrayList(); + for (int i = 0; i < mtexsArray.getTotalSize(); ++i) { + Pointer p = mtexsArray.get(i); + if (p.isNotNull() && (separatedTextures & 1 << i) == 0) { + TextureData textureData = new TextureData(); + textureData.mtex = p.fetchData(blenderContext.getInputStream()).get(0); + textureData.uvCoordinatesType = ((Number) textureData.mtex.getFieldValue("texco")).intValue(); + textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue(); + + Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex"); + if (pTex.isNotNull()) { + Structure tex = pTex.fetchData(blenderContext.getInputStream()).get(0); + textureData.textureStructure = tex; + texturesList.add(textureData); + } + } + } + + // loading the textures and merging them + Map> textureDataMap = this.sortAndFilterTextures(texturesList); + loadedTextures = new HashMap(); + float[] diffuseColorArray = new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a }; + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + for (Entry> entry : textureDataMap.entrySet()) { + if (entry.getValue().size() > 0) { + CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue()); + for (TextureData textureData : entry.getValue()) { + int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); + boolean negateTexture = (texflag & 0x04) != 0; + Texture texture = textureHelper.getTexture(textureData.textureStructure, textureData.mtex, blenderContext); + if (texture != null) { + int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue(); + float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() }; + float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue(); + TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac); + combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, textureData.textureStructure, blenderContext); + } + } + if (combinedTexture.getTexturesCount() > 0) { + loadedTextures.put(entry.getKey(), combinedTexture); + } + } + } + + // veryfying if the transparency is present + // (in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when + // it is not required + boolean transparent = false; + if (diffuseColor != null) { + transparent = diffuseColor.a < 1.0f; + if (textureDataMap.size() > 0) {// texutre covers the material color + diffuseColor.set(1, 1, 1, 1); + } + } + if (specularColor != null) { + transparent = transparent || specularColor.a < 1.0f; + } + if (ambientColor != null) { + transparent = transparent || ambientColor.a < 1.0f; + } + this.transparent = transparent; + } + + public void applyMaterial(Geometry geometry, Long geometriesOMA, List userDefinedUVCoordinates, BlenderContext blenderContext) { + Material material = null; + if (shadeless) { + material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + if (!transparent) { diffuseColor.a = 1; } - + material.setColor("Color", diffuseColor); - } else { - material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md"); - material.setBoolean("UseMaterialColors", Boolean.TRUE); - - // setting the colors - material.setBoolean("Minnaert", diffuseShader == DiffuseShader.MINNAERT); - if (!transparent) { - diffuseColor.a = 1; - } - material.setColor("Diffuse", diffuseColor); - - material.setBoolean("WardIso", specularShader == SpecularShader.WARDISO); - material.setColor("Specular", specularColor); - - material.setColor("Ambient", ambientColor); - material.setFloat("Shininess", shininess); - } - - //applying textures - if(loadedTextures != null && loadedTextures.size() > 0) { - Entry basicUVSOwner = null; - for(Entry entry : loadedTextures.entrySet()) { - CombinedTexture combinedTexture = entry.getValue(); - combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext); - - if(basicUVSOwner == null) { - basicUVSOwner = entry; - } else { - combinedTexture.castToUVS(basicUVSOwner.getValue(), blenderContext); - this.setTexture(material, entry.getKey().intValue(), combinedTexture.getResultTexture()); - } - } - - if(basicUVSOwner != null) { - this.setTexture(material, basicUVSOwner.getKey().intValue(), basicUVSOwner.getValue().getResultTexture()); - List basicUVS = basicUVSOwner.getValue().getResultUVS(); - VertexBuffer uvCoordsBuffer = new VertexBuffer(VertexBuffer.Type.TexCoord); - uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(basicUVS.toArray(new Vector2f[basicUVS.size()]))); - geometry.getMesh().setBuffer(uvCoordsBuffer); - } - } else if(userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { - VertexBuffer uvCoordsBuffer = new VertexBuffer(VertexBuffer.Type.TexCoord); - uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, - BufferUtils.createFloatBuffer(userDefinedUVCoordinates.toArray(new Vector2f[userDefinedUVCoordinates.size()]))); - geometry.getMesh().setBuffer(uvCoordsBuffer); - } - - //applying additional data - material.setName(name); - if (vertexColor) { - material.setBoolean(shadeless ? "VertexColor" : "UseVertexColor", true); - } - if(this.faceCullMode != null) { - material.getAdditionalRenderState().setFaceCullMode(faceCullMode); - } else { - material.getAdditionalRenderState().setFaceCullMode(blenderContext.getBlenderKey().getFaceCullMode()); - } + } else { + material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md"); + material.setBoolean("UseMaterialColors", Boolean.TRUE); + + // setting the colors + material.setBoolean("Minnaert", diffuseShader == DiffuseShader.MINNAERT); + if (!transparent) { + diffuseColor.a = 1; + } + material.setColor("Diffuse", diffuseColor); + + material.setBoolean("WardIso", specularShader == SpecularShader.WARDISO); + material.setColor("Specular", specularColor); + + material.setColor("Ambient", ambientColor); + material.setFloat("Shininess", shininess); + } + + // applying textures + if (loadedTextures != null && loadedTextures.size() > 0) { + Entry basicUVSOwner = null; + for (Entry entry : loadedTextures.entrySet()) { + CombinedTexture combinedTexture = entry.getValue(); + combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext); + + if (basicUVSOwner == null) { + basicUVSOwner = entry; + } else { + combinedTexture.castToUVS(basicUVSOwner.getValue(), blenderContext); + this.setTexture(material, entry.getKey().intValue(), combinedTexture.getResultTexture()); + } + } + + if (basicUVSOwner != null) { + this.setTexture(material, basicUVSOwner.getKey().intValue(), basicUVSOwner.getValue().getResultTexture()); + List basicUVS = basicUVSOwner.getValue().getResultUVS(); + VertexBuffer uvCoordsBuffer = new VertexBuffer(VertexBuffer.Type.TexCoord); + uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(basicUVS.toArray(new Vector2f[basicUVS.size()]))); + geometry.getMesh().setBuffer(uvCoordsBuffer); + } + } else if (userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { + VertexBuffer uvCoordsBuffer = new VertexBuffer(VertexBuffer.Type.TexCoord); + uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(userDefinedUVCoordinates.toArray(new Vector2f[userDefinedUVCoordinates.size()]))); + geometry.getMesh().setBuffer(uvCoordsBuffer); + } + + // applying additional data + material.setName(name); + if (vertexColor) { + material.setBoolean(shadeless ? "VertexColor" : "UseVertexColor", true); + } + if (this.faceCullMode != null) { + material.getAdditionalRenderState().setFaceCullMode(faceCullMode); + } else { + material.getAdditionalRenderState().setFaceCullMode(blenderContext.getBlenderKey().getFaceCullMode()); + } if (transparent) { - material.setTransparent(true); - material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + material.setTransparent(true); + material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); geometry.setQueueBucket(Bucket.Transparent); } - + geometry.setMaterial(material); - } - - /** - * Sets the texture to the given material. - * - * @param material - * the material that we add texture to - * @param mapTo - * the texture mapping type - * @param texture - * the added texture - */ - private void setTexture(Material material, int mapTo, Texture texture) { - switch (mapTo) { - case MTEX_COL: - material.setTexture(shadeless ? MaterialHelper.TEXTURE_TYPE_COLOR : MaterialHelper.TEXTURE_TYPE_DIFFUSE, texture); - break; - case MTEX_NOR: - material.setTexture(MaterialHelper.TEXTURE_TYPE_NORMAL, texture); - break; - case MTEX_SPEC: - material.setTexture(MaterialHelper.TEXTURE_TYPE_SPECULAR, texture); - break; - case MTEX_EMIT: - material.setTexture(MaterialHelper.TEXTURE_TYPE_GLOW, texture); - break; - case MTEX_ALPHA: - if(!shadeless) { - material.setTexture(MaterialHelper.TEXTURE_TYPE_ALPHA, texture); - } else { - LOGGER.warning("JME does not support alpha map on unshaded material. Material name is " + name); - } - break; - default: - LOGGER.severe("Unknown mapping type: " + mapTo); - } - } - - /** - * @return true if the material has at least one generated texture and false otherwise - */ - public boolean hasGeneratedTextures() { - if(loadedTextures != null) { - for(Entry entry : loadedTextures.entrySet()) { - if(entry.getValue().hasGeneratedTextures()) { - return true; - } - } - } - return false; - } - - /** - * This method sorts the textures by their mapping type. In each group only - * textures of one type are put (either two- or three-dimensional). If the - * mapping type is MTEX_COL then if the texture has no alpha channel then - * all textures before it are discarded and will not be loaded and merged - * because texture with no alpha will cover them anyway. - * - * @return a map with sorted and filtered textures - */ - private Map> sortAndFilterTextures(List textures) { - int[] mappings = new int[] { MTEX_COL, MTEX_NOR, MTEX_EMIT, MTEX_SPEC, MTEX_ALPHA }; - Map> result = new HashMap>(); - for (TextureData data : textures) { - Number mapto = (Number) data.mtex.getFieldValue("mapto"); - for (int i = 0; i < mappings.length; ++i) { - if((mappings[i] & mapto.intValue()) != 0) { - List datas = result.get(mappings[i]); - if (datas == null) { - datas = new ArrayList(); - result.put(mappings[i], datas); - } - datas.add(data); - } - } - } - return result; - } - - /** - * This method sets the face cull mode. - * @param faceCullMode the face cull mode - */ - public void setFaceCullMode(FaceCullMode faceCullMode) { - this.faceCullMode = faceCullMode; - } - - /** - * @return the face cull mode - */ - public FaceCullMode getFaceCullMode() { - return faceCullMode; - } - - /** - * This method returns the diffuse color. - * - * @param materialStructure the material structure - * @param diffuseShader the diffuse shader - * @return the diffuse color - */ - private ColorRGBA readDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) { - // bitwise 'or' of all textures mappings - int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue(); - - // diffuse color - float r = ((Number) materialStructure.getFieldValue("r")).floatValue(); - float g = ((Number) materialStructure.getFieldValue("g")).floatValue(); - float b = ((Number) materialStructure.getFieldValue("b")).floatValue(); - float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); - if ((commonMapto & 0x01) == 0x01) {// Col - return new ColorRGBA(r, g, b, alpha); - } else { - switch (diffuseShader) { - case FRESNEL: - case ORENNAYAR: - case TOON: - break;// TODO: find what is the proper modification - case MINNAERT: - case LAMBERT:// TODO: check if that is correct - float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue(); - r *= ref; - g *= ref; - b *= ref; - break; - default: - throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString()); - } - return new ColorRGBA(r, g, b, alpha); - } - } - - /** - * This method returns a specular color used by the material. - * - * @param materialStructure - * the material structure filled with data - * @return a specular color used by the material - */ - private ColorRGBA readSpecularColor(Structure materialStructure, SpecularShader specularShader) { - float r = ((Number) materialStructure.getFieldValue("specr")).floatValue(); - float g = ((Number) materialStructure.getFieldValue("specg")).floatValue(); - float b = ((Number) materialStructure.getFieldValue("specb")).floatValue(); - float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); - switch (specularShader) { - case BLINN: - case COOKTORRENCE: - case TOON: - case WARDISO:// TODO: find what is the proper modification - break; - case PHONG:// TODO: check if that is correct - float spec = ((Number) materialStructure.getFieldValue("spec")).floatValue(); - r *= spec * 0.5f; - g *= spec * 0.5f; - b *= spec * 0.5f; - break; - default: - throw new IllegalStateException("Unknown specular shader type: " + specularShader.toString()); - } - return new ColorRGBA(r, g, b, alpha); - } - - private static class TextureData { - Structure mtex; - Structure textureStructure; - int uvCoordinatesType; - int projectionType; - } + } + + /** + * Sets the texture to the given material. + * + * @param material + * the material that we add texture to + * @param mapTo + * the texture mapping type + * @param texture + * the added texture + */ + private void setTexture(Material material, int mapTo, Texture texture) { + switch (mapTo) { + case MTEX_COL: + material.setTexture(shadeless ? MaterialHelper.TEXTURE_TYPE_COLOR : MaterialHelper.TEXTURE_TYPE_DIFFUSE, texture); + break; + case MTEX_NOR: + material.setTexture(MaterialHelper.TEXTURE_TYPE_NORMAL, texture); + break; + case MTEX_SPEC: + material.setTexture(MaterialHelper.TEXTURE_TYPE_SPECULAR, texture); + break; + case MTEX_EMIT: + material.setTexture(MaterialHelper.TEXTURE_TYPE_GLOW, texture); + break; + case MTEX_ALPHA: + if (!shadeless) { + material.setTexture(MaterialHelper.TEXTURE_TYPE_ALPHA, texture); + } else { + LOGGER.warning("JME does not support alpha map on unshaded material. Material name is " + name); + } + break; + default: + LOGGER.severe("Unknown mapping type: " + mapTo); + } + } + + /** + * @return true if the material has at least one generated texture and false otherwise + */ + public boolean hasGeneratedTextures() { + if (loadedTextures != null) { + for (Entry entry : loadedTextures.entrySet()) { + if (entry.getValue().hasGeneratedTextures()) { + return true; + } + } + } + return false; + } + + /** + * This method sorts the textures by their mapping type. In each group only + * textures of one type are put (either two- or three-dimensional). If the + * mapping type is MTEX_COL then if the texture has no alpha channel then + * all textures before it are discarded and will not be loaded and merged + * because texture with no alpha will cover them anyway. + * + * @return a map with sorted and filtered textures + */ + private Map> sortAndFilterTextures(List textures) { + int[] mappings = new int[] { MTEX_COL, MTEX_NOR, MTEX_EMIT, MTEX_SPEC, MTEX_ALPHA }; + Map> result = new HashMap>(); + for (TextureData data : textures) { + Number mapto = (Number) data.mtex.getFieldValue("mapto"); + for (int i = 0; i < mappings.length; ++i) { + if ((mappings[i] & mapto.intValue()) != 0) { + List datas = result.get(mappings[i]); + if (datas == null) { + datas = new ArrayList(); + result.put(mappings[i], datas); + } + datas.add(data); + } + } + } + return result; + } + + /** + * This method sets the face cull mode. + * @param faceCullMode + * the face cull mode + */ + public void setFaceCullMode(FaceCullMode faceCullMode) { + this.faceCullMode = faceCullMode; + } + + /** + * @return the face cull mode + */ + public FaceCullMode getFaceCullMode() { + return faceCullMode; + } + + /** + * This method returns the diffuse color. + * + * @param materialStructure + * the material structure + * @param diffuseShader + * the diffuse shader + * @return the diffuse color + */ + private ColorRGBA readDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) { + // bitwise 'or' of all textures mappings + int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue(); + + // diffuse color + float r = ((Number) materialStructure.getFieldValue("r")).floatValue(); + float g = ((Number) materialStructure.getFieldValue("g")).floatValue(); + float b = ((Number) materialStructure.getFieldValue("b")).floatValue(); + float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); + if ((commonMapto & 0x01) == 0x01) {// Col + return new ColorRGBA(r, g, b, alpha); + } else { + switch (diffuseShader) { + case FRESNEL: + case ORENNAYAR: + case TOON: + break;// TODO: find what is the proper modification + case MINNAERT: + case LAMBERT:// TODO: check if that is correct + float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue(); + r *= ref; + g *= ref; + b *= ref; + break; + default: + throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString()); + } + return new ColorRGBA(r, g, b, alpha); + } + } + + /** + * This method returns a specular color used by the material. + * + * @param materialStructure + * the material structure filled with data + * @return a specular color used by the material + */ + private ColorRGBA readSpecularColor(Structure materialStructure, SpecularShader specularShader) { + float r = ((Number) materialStructure.getFieldValue("specr")).floatValue(); + float g = ((Number) materialStructure.getFieldValue("specg")).floatValue(); + float b = ((Number) materialStructure.getFieldValue("specb")).floatValue(); + float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); + switch (specularShader) { + case BLINN: + case COOKTORRENCE: + case TOON: + case WARDISO:// TODO: find what is the proper modification + break; + case PHONG:// TODO: check if that is correct + float spec = ((Number) materialStructure.getFieldValue("spec")).floatValue(); + r *= spec * 0.5f; + g *= spec * 0.5f; + b *= spec * 0.5f; + break; + default: + throw new IllegalStateException("Unknown specular shader type: " + specularShader.toString()); + } + return new ColorRGBA(r, g, b, alpha); + } + + private static class TextureData { + Structure mtex; + Structure textureStructure; + int uvCoordinatesType; + int projectionType; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialHelper.java index 0eca86089..9a99f998a 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/materials/MaterialHelper.java @@ -57,410 +57,411 @@ import java.util.logging.Level; import java.util.logging.Logger; public class MaterialHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName()); - protected static final float DEFAULT_SHININESS = 20.0f; - - public static final String TEXTURE_TYPE_COLOR = "ColorMap"; - public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap"; - public static final String TEXTURE_TYPE_NORMAL = "NormalMap"; - public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap"; - public static final String TEXTURE_TYPE_GLOW = "GlowMap"; - public static final String TEXTURE_TYPE_ALPHA = "AlphaMap"; - - public static final Integer ALPHA_MASK_NONE = Integer.valueOf(0); - public static final Integer ALPHA_MASK_CIRCLE = Integer.valueOf(1); - public static final Integer ALPHA_MASK_CONE = Integer.valueOf(2); - public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3); - protected final Map alphaMasks = new HashMap(); - - /** - * The type of the material's diffuse shader. - */ - public static enum DiffuseShader { - LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL - } - - /** - * The type of the material's specular shader. - */ - public static enum SpecularShader { - COOKTORRENCE, PHONG, BLINN, TOON, WARDISO - } - - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender - * versions. - * - * @param blenderVersion - * the version read from the blend file - * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not - */ - public MaterialHelper(String blenderVersion, boolean fixUpAxis) { - super(blenderVersion, false); - // setting alpha masks - alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() { - public void setImageSize(int width, int height) {} - - public byte getAlpha(float x, float y) { - return (byte) 255; - } - }); - alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() { - private float r; - private float[] center; - - public void setImageSize(int width, int height) { - r = Math.min(width, height) * 0.5f; - center = new float[] { width * 0.5f, height * 0.5f }; - } - - public byte getAlpha(float x, float y) { - float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); - return (byte) (d >= r ? 0 : 255); - } - }); - alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() { - private float r; - private float[] center; - - public void setImageSize(int width, int height) { - r = Math.min(width, height) * 0.5f; - center = new float[] { width * 0.5f, height * 0.5f }; - } - - public byte getAlpha(float x, float y) { - float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); - return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f); - } - }); - alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() { - private float r; - private float[] center; - - public void setImageSize(int width, int height) { - r = Math.min(width, height) * 0.5f; - center = new float[] { width * 0.5f, height * 0.5f }; - } - - public byte getAlpha(float x, float y) { - float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r; - return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f); - } - }); - } - - /** - * This method converts the material structure to jme Material. - * @param structure - * structure with material data - * @param blenderContext - * the blender context - * @return jme material - * @throws BlenderFileException - * an exception is throw when problems with blend file occur - */ - public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Loading material."); - MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); - if (result != null) { - return result; - } - - result = new MaterialContext(structure, blenderContext); - LOGGER.log(Level.FINE, "Material''s name: {0}", result.name); - blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result); - return result; - } - - /** - * This method returns a material similar to the one given but without textures. If the material has no textures it is not cloned but - * returned itself. - * - * @param material - * a material to be cloned without textures - * @param imageType - * type of image defined by blender; the constants are defined in TextureHelper - * @return material without textures of a specified type - */ - public Material getNonTexturedMaterial(Material material, int imageType) { - String[] textureParamNames = new String[] { TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_NORMAL, TEXTURE_TYPE_GLOW, TEXTURE_TYPE_SPECULAR, TEXTURE_TYPE_ALPHA }; - Map textures = new HashMap(textureParamNames.length); - for (String textureParamName : textureParamNames) { - MatParamTexture matParamTexture = material.getTextureParam(textureParamName); - if (matParamTexture != null) { - textures.put(textureParamName, matParamTexture.getTextureValue()); - } - } - if (textures.isEmpty()) { - return material; - } else { - // clear all textures first so that wo de not waste resources cloning them - for (Entry textureParamName : textures.entrySet()) { - String name = textureParamName.getValue().getName(); - try { - int type = Integer.parseInt(name); - if (type == imageType) { - material.clearParam(textureParamName.getKey()); - } - } catch (NumberFormatException e) { - LOGGER.log(Level.WARNING, "The name of the texture does not contain the texture type value! {0} will not be removed!", name); - } - } - Material result = material.clone(); - // put the textures back in place - for (Entry textureEntry : textures.entrySet()) { - material.setTexture(textureEntry.getKey(), textureEntry.getValue()); - } - return result; - } - } - - /** - * This method converts the given material into particles-usable material. - * The texture and glow color are being copied. - * The method assumes it receives the Lighting type of material. - * @param material - * the source material - * @param blenderContext - * the blender context - * @return material converted into particles-usable material - */ - public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, BlenderContext blenderContext) { - Material result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); - - // copying texture - MatParam diffuseMap = material.getParam("DiffuseMap"); - if (diffuseMap != null) { - Texture texture = ((Texture) diffuseMap.getValue()).clone(); - - // applying alpha mask to the texture - Image image = texture.getImage(); - ByteBuffer sourceBB = image.getData(0); - sourceBB.rewind(); - int w = image.getWidth(); - int h = image.getHeight(); - ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4); - IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex); - iAlphaMask.setImageSize(w, h); - - for (int x = 0; x < w; ++x) { - for (int y = 0; y < h; ++y) { - bb.put(sourceBB.get()); - bb.put(sourceBB.get()); - bb.put(sourceBB.get()); - bb.put(iAlphaMask.getAlpha(x, y)); - } - } - - image = new Image(Format.RGBA8, w, h, bb); - texture.setImage(image); - - result.setTextureParam("Texture", VarType.Texture2D, texture); - } - - // copying glow color - MatParam glowColor = material.getParam("GlowColor"); - if (glowColor != null) { - ColorRGBA color = (ColorRGBA) glowColor.getValue(); - result.setParam("GlowColor", VarType.Vector3, color); - } - return result; - } - - /** - * This method indicates if the material has any kind of texture. - * - * @param material - * the material - * @return true if the texture exists in the material and false otherwise - */ - public boolean hasTexture(Material material) { - if (material != null) { - if (material.getTextureParam(TEXTURE_TYPE_ALPHA) != null) { - return true; - } - if (material.getTextureParam(TEXTURE_TYPE_COLOR) != null) { - return true; - } - if (material.getTextureParam(TEXTURE_TYPE_DIFFUSE) != null) { - return true; - } - if (material.getTextureParam(TEXTURE_TYPE_GLOW) != null) { - return true; - } - if (material.getTextureParam(TEXTURE_TYPE_NORMAL) != null) { - return true; - } - if (material.getTextureParam(TEXTURE_TYPE_SPECULAR) != null) { - return true; - } - } - return false; - } - - /** - * This method indicates if the material has a texture of a specified type. - * - * @param material - * the material - * @param textureType - * the type of the texture - * @return true if the texture exists in the material and false otherwise - */ - public boolean hasTexture(Material material, String textureType) { - if (material != null) { - return material.getTextureParam(textureType) != null; - } - return false; - } - - /** - * This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or - * curve) but needs to have 'mat' field/ - * - * @param structureWithMaterials - * the structure containing the mesh data - * @param blenderContext - * the blender context - * @return a list of vertices colors, each color belongs to a single vertex - * @throws BlenderFileException - * this exception is thrown when the blend file structure is somehow invalid or corrupted - */ - public MaterialContext[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException { - Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat"); - MaterialContext[] materials = null; - if (ppMaterials.isNotNull()) { - List materialStructures = ppMaterials.fetchData(blenderContext.getInputStream()); - if (materialStructures != null && materialStructures.size() > 0) { - MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); - materials = new MaterialContext[materialStructures.size()]; - int i = 0; - for (Structure s : materialStructures) { - materials[i++] = s == null ? null : materialHelper.toMaterialContext(s, blenderContext); - } - } - } - return materials; - } - - /** - * This method converts rgb values to hsv values. - * - * @param r - * red value of the color - * @param g - * green value of the color - * @param b - * blue value of the color - * @param hsv - * hsv values of a color (this table contains the result of the transformation) - */ - public void rgbToHsv(float r, float g, float b, float[] hsv) { - float cmax = r; - float cmin = r; - cmax = g > cmax ? g : cmax; - cmin = g < cmin ? g : cmin; - cmax = b > cmax ? b : cmax; - cmin = b < cmin ? b : cmin; - - hsv[2] = cmax; /* value */ - if (cmax != 0.0) { - hsv[1] = (cmax - cmin) / cmax; - } else { - hsv[1] = 0.0f; - hsv[0] = 0.0f; - } - if (hsv[1] == 0.0) { - hsv[0] = -1.0f; - } else { - float cdelta = cmax - cmin; - float rc = (cmax - r) / cdelta; - float gc = (cmax - g) / cdelta; - float bc = (cmax - b) / cdelta; - if (r == cmax) { - hsv[0] = bc - gc; - } else if (g == cmax) { - hsv[0] = 2.0f + rc - bc; - } else { - hsv[0] = 4.0f + gc - rc; - } - hsv[0] *= 60.0f; - if (hsv[0] < 0.0f) { - hsv[0] += 360.0f; - } - } - - hsv[0] /= 360.0f; - if (hsv[0] < 0.0f) { - hsv[0] = 0.0f; - } - } - - /** - * This method converts rgb values to hsv values. - * - * @param h - * hue - * @param s - * saturation - * @param v - * value - * @param rgb - * rgb result vector (should have 3 elements) - */ - public void hsvToRgb(float h, float s, float v, float[] rgb) { - h *= 360.0f; - if (s == 0.0) { - rgb[0] = rgb[1] = rgb[2] = v; - } else { - if (h == 360) { - h = 0; - } else { - h /= 60; - } - int i = (int) Math.floor(h); - float f = h - i; - float p = v * (1.0f - s); - float q = v * (1.0f - s * f); - float t = v * (1.0f - s * (1.0f - f)); - switch (i) { - case 0: - rgb[0] = v; - rgb[1] = t; - rgb[2] = p; - break; - case 1: - rgb[0] = q; - rgb[1] = v; - rgb[2] = p; - break; - case 2: - rgb[0] = p; - rgb[1] = v; - rgb[2] = t; - break; - case 3: - rgb[0] = p; - rgb[1] = q; - rgb[2] = v; - break; - case 4: - rgb[0] = t; - rgb[1] = p; - rgb[2] = v; - break; - case 5: - rgb[0] = v; - rgb[1] = p; - rgb[2] = q; - break; - } - } - } - - @Override - public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0; - } + private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName()); + protected static final float DEFAULT_SHININESS = 20.0f; + + public static final String TEXTURE_TYPE_COLOR = "ColorMap"; + public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap"; + public static final String TEXTURE_TYPE_NORMAL = "NormalMap"; + public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap"; + public static final String TEXTURE_TYPE_GLOW = "GlowMap"; + public static final String TEXTURE_TYPE_ALPHA = "AlphaMap"; + + public static final Integer ALPHA_MASK_NONE = Integer.valueOf(0); + public static final Integer ALPHA_MASK_CIRCLE = Integer.valueOf(1); + public static final Integer ALPHA_MASK_CONE = Integer.valueOf(2); + public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3); + protected final Map alphaMasks = new HashMap(); + + /** + * The type of the material's diffuse shader. + */ + public static enum DiffuseShader { + LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL + } + + /** + * The type of the material's specular shader. + */ + public static enum SpecularShader { + COOKTORRENCE, PHONG, BLINN, TOON, WARDISO + } + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender + * versions. + * + * @param blenderVersion + * the version read from the blend file + * @param fixUpAxis + * a variable that indicates if the Y asxis is the UP axis or not + */ + public MaterialHelper(String blenderVersion, boolean fixUpAxis) { + super(blenderVersion, false); + // setting alpha masks + alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() { + public void setImageSize(int width, int height) { + } + + public byte getAlpha(float x, float y) { + return (byte) 255; + } + }); + alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() { + private float r; + private float[] center; + + public void setImageSize(int width, int height) { + r = Math.min(width, height) * 0.5f; + center = new float[] { width * 0.5f, height * 0.5f }; + } + + public byte getAlpha(float x, float y) { + float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); + return (byte) (d >= r ? 0 : 255); + } + }); + alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() { + private float r; + private float[] center; + + public void setImageSize(int width, int height) { + r = Math.min(width, height) * 0.5f; + center = new float[] { width * 0.5f, height * 0.5f }; + } + + public byte getAlpha(float x, float y) { + float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); + return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f); + } + }); + alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() { + private float r; + private float[] center; + + public void setImageSize(int width, int height) { + r = Math.min(width, height) * 0.5f; + center = new float[] { width * 0.5f, height * 0.5f }; + } + + public byte getAlpha(float x, float y) { + float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r; + return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f); + } + }); + } + + /** + * This method converts the material structure to jme Material. + * @param structure + * structure with material data + * @param blenderContext + * the blender context + * @return jme material + * @throws BlenderFileException + * an exception is throw when problems with blend file occur + */ + public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Loading material."); + MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (result != null) { + return result; + } + + result = new MaterialContext(structure, blenderContext); + LOGGER.log(Level.FINE, "Material''s name: {0}", result.name); + blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, result); + return result; + } + + /** + * This method returns a material similar to the one given but without textures. If the material has no textures it is not cloned but + * returned itself. + * + * @param material + * a material to be cloned without textures + * @param imageType + * type of image defined by blender; the constants are defined in TextureHelper + * @return material without textures of a specified type + */ + public Material getNonTexturedMaterial(Material material, int imageType) { + String[] textureParamNames = new String[] { TEXTURE_TYPE_DIFFUSE, TEXTURE_TYPE_NORMAL, TEXTURE_TYPE_GLOW, TEXTURE_TYPE_SPECULAR, TEXTURE_TYPE_ALPHA }; + Map textures = new HashMap(textureParamNames.length); + for (String textureParamName : textureParamNames) { + MatParamTexture matParamTexture = material.getTextureParam(textureParamName); + if (matParamTexture != null) { + textures.put(textureParamName, matParamTexture.getTextureValue()); + } + } + if (textures.isEmpty()) { + return material; + } else { + // clear all textures first so that wo de not waste resources cloning them + for (Entry textureParamName : textures.entrySet()) { + String name = textureParamName.getValue().getName(); + try { + int type = Integer.parseInt(name); + if (type == imageType) { + material.clearParam(textureParamName.getKey()); + } + } catch (NumberFormatException e) { + LOGGER.log(Level.WARNING, "The name of the texture does not contain the texture type value! {0} will not be removed!", name); + } + } + Material result = material.clone(); + // put the textures back in place + for (Entry textureEntry : textures.entrySet()) { + material.setTexture(textureEntry.getKey(), textureEntry.getValue()); + } + return result; + } + } + + /** + * This method converts the given material into particles-usable material. + * The texture and glow color are being copied. + * The method assumes it receives the Lighting type of material. + * @param material + * the source material + * @param blenderContext + * the blender context + * @return material converted into particles-usable material + */ + public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, BlenderContext blenderContext) { + Material result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); + + // copying texture + MatParam diffuseMap = material.getParam("DiffuseMap"); + if (diffuseMap != null) { + Texture texture = ((Texture) diffuseMap.getValue()).clone(); + + // applying alpha mask to the texture + Image image = texture.getImage(); + ByteBuffer sourceBB = image.getData(0); + sourceBB.rewind(); + int w = image.getWidth(); + int h = image.getHeight(); + ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4); + IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex); + iAlphaMask.setImageSize(w, h); + + for (int x = 0; x < w; ++x) { + for (int y = 0; y < h; ++y) { + bb.put(sourceBB.get()); + bb.put(sourceBB.get()); + bb.put(sourceBB.get()); + bb.put(iAlphaMask.getAlpha(x, y)); + } + } + + image = new Image(Format.RGBA8, w, h, bb); + texture.setImage(image); + + result.setTextureParam("Texture", VarType.Texture2D, texture); + } + + // copying glow color + MatParam glowColor = material.getParam("GlowColor"); + if (glowColor != null) { + ColorRGBA color = (ColorRGBA) glowColor.getValue(); + result.setParam("GlowColor", VarType.Vector3, color); + } + return result; + } + + /** + * This method indicates if the material has any kind of texture. + * + * @param material + * the material + * @return true if the texture exists in the material and false otherwise + */ + public boolean hasTexture(Material material) { + if (material != null) { + if (material.getTextureParam(TEXTURE_TYPE_ALPHA) != null) { + return true; + } + if (material.getTextureParam(TEXTURE_TYPE_COLOR) != null) { + return true; + } + if (material.getTextureParam(TEXTURE_TYPE_DIFFUSE) != null) { + return true; + } + if (material.getTextureParam(TEXTURE_TYPE_GLOW) != null) { + return true; + } + if (material.getTextureParam(TEXTURE_TYPE_NORMAL) != null) { + return true; + } + if (material.getTextureParam(TEXTURE_TYPE_SPECULAR) != null) { + return true; + } + } + return false; + } + + /** + * This method indicates if the material has a texture of a specified type. + * + * @param material + * the material + * @param textureType + * the type of the texture + * @return true if the texture exists in the material and false otherwise + */ + public boolean hasTexture(Material material, String textureType) { + if (material != null) { + return material.getTextureParam(textureType) != null; + } + return false; + } + + /** + * This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or + * curve) but needs to have 'mat' field/ + * + * @param structureWithMaterials + * the structure containing the mesh data + * @param blenderContext + * the blender context + * @return a list of vertices colors, each color belongs to a single vertex + * @throws BlenderFileException + * this exception is thrown when the blend file structure is somehow invalid or corrupted + */ + public MaterialContext[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException { + Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat"); + MaterialContext[] materials = null; + if (ppMaterials.isNotNull()) { + List materialStructures = ppMaterials.fetchData(blenderContext.getInputStream()); + if (materialStructures != null && materialStructures.size() > 0) { + MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + materials = new MaterialContext[materialStructures.size()]; + int i = 0; + for (Structure s : materialStructures) { + materials[i++] = s == null ? null : materialHelper.toMaterialContext(s, blenderContext); + } + } + } + return materials; + } + + /** + * This method converts rgb values to hsv values. + * + * @param r + * red value of the color + * @param g + * green value of the color + * @param b + * blue value of the color + * @param hsv + * hsv values of a color (this table contains the result of the transformation) + */ + public void rgbToHsv(float r, float g, float b, float[] hsv) { + float cmax = r; + float cmin = r; + cmax = g > cmax ? g : cmax; + cmin = g < cmin ? g : cmin; + cmax = b > cmax ? b : cmax; + cmin = b < cmin ? b : cmin; + + hsv[2] = cmax; /* value */ + if (cmax != 0.0) { + hsv[1] = (cmax - cmin) / cmax; + } else { + hsv[1] = 0.0f; + hsv[0] = 0.0f; + } + if (hsv[1] == 0.0) { + hsv[0] = -1.0f; + } else { + float cdelta = cmax - cmin; + float rc = (cmax - r) / cdelta; + float gc = (cmax - g) / cdelta; + float bc = (cmax - b) / cdelta; + if (r == cmax) { + hsv[0] = bc - gc; + } else if (g == cmax) { + hsv[0] = 2.0f + rc - bc; + } else { + hsv[0] = 4.0f + gc - rc; + } + hsv[0] *= 60.0f; + if (hsv[0] < 0.0f) { + hsv[0] += 360.0f; + } + } + + hsv[0] /= 360.0f; + if (hsv[0] < 0.0f) { + hsv[0] = 0.0f; + } + } + + /** + * This method converts rgb values to hsv values. + * + * @param h + * hue + * @param s + * saturation + * @param v + * value + * @param rgb + * rgb result vector (should have 3 elements) + */ + public void hsvToRgb(float h, float s, float v, float[] rgb) { + h *= 360.0f; + if (s == 0.0) { + rgb[0] = rgb[1] = rgb[2] = v; + } else { + if (h == 360) { + h = 0; + } else { + h /= 60; + } + int i = (int) Math.floor(h); + float f = h - i; + float p = v * (1.0f - s); + float q = v * (1.0f - s * f); + float t = v * (1.0f - s * (1.0f - f)); + switch (i) { + case 0: + rgb[0] = v; + rgb[1] = t; + rgb[2] = p; + break; + case 1: + rgb[0] = q; + rgb[1] = v; + rgb[2] = p; + break; + case 2: + rgb[0] = p; + rgb[1] = v; + rgb[2] = t; + break; + case 3: + rgb[0] = p; + rgb[1] = q; + rgb[2] = v; + break; + case 4: + rgb[0] = t; + rgb[1] = p; + rgb[2] = v; + break; + case 5: + rgb[0] = v; + rgb[1] = p; + rgb[2] = q; + break; + } + } + } + + @Override + public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { + return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java index 13a58d55c..cac38ff66 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshBuilder.java @@ -12,85 +12,103 @@ import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.util.BufferUtils; -/*package*/ class MeshBuilder { - private static final Logger LOGGER = Logger.getLogger(MeshBuilder.class.getName()); - - /** An array of reference vertices. */ - private Vector3f[][] verticesAndNormals; - /** An list of vertices colors. */ - private List verticesColors; - /** A variable that indicates if the model uses generated textures. */ - private boolean usesGeneratedTextures; - - /** This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' - positions (it simply tells which vertex is referenced where in the result list). */ +/*package*/class MeshBuilder { + private static final Logger LOGGER = Logger.getLogger(MeshBuilder.class.getName()); + + /** An array of reference vertices. */ + private Vector3f[][] verticesAndNormals; + /** An list of vertices colors. */ + private List verticesColors; + /** A variable that indicates if the model uses generated textures. */ + private boolean usesGeneratedTextures; + + /** + * This map's key is the vertex index from 'vertices 'table and the value are indices from 'vertexList' + * positions (it simply tells which vertex is referenced where in the result list). + */ private Map>> globalVertexReferenceMap; - + /** A map between vertex index and its UV coordinates. */ - private Map uvsMap = new HashMap(); + private Map uvsMap = new HashMap(); /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ - private Map> normalMap = new HashMap>(); + private Map> normalMap = new HashMap>(); /** The following map sorts vertices by material number (because in jme Mesh can have only one material). */ - private Map> vertexMap = new HashMap>(); + private Map> vertexMap = new HashMap>(); /** The following map sorts vertices colors by material number (because in jme Mesh can have only one material). */ - private Map> vertexColorsMap = new HashMap>(); + private Map> vertexColorsMap = new HashMap>(); /** The following map sorts indexes by material number (because in jme Mesh can have only one material). */ - private Map> indexMap = new HashMap>(); + private Map> indexMap = new HashMap>(); /** A map between material number and UV coordinates of mesh that has this material applied. */ - private Map> uvCoordinates = new HashMap>();// - + private Map> uvCoordinates = new HashMap>(); // + /** * Constructor. Stores the given array (not copying it). * The second argument describes if the model uses generated textures. If yes then no vertex amount optimisation is applied. * The amount of vertices is always faceCount * 3. - * @param verticesAndNormals the reference vertices and normals array - * @param usesGeneratedTextures a variable that indicates if the model uses generated textures or not + * @param verticesAndNormals + * the reference vertices and normals array + * @param usesGeneratedTextures + * a variable that indicates if the model uses generated textures or not */ - public MeshBuilder(Vector3f[][] verticesAndNormals, List verticesColors, boolean usesGeneratedTextures) { - this.verticesAndNormals = verticesAndNormals; - this.verticesColors = verticesColors; - this.usesGeneratedTextures = usesGeneratedTextures; - globalVertexReferenceMap = new HashMap>>(verticesAndNormals.length); - } - - /** - * This method adds a point to the mesh. - * @param coordinates the coordinates of the point - * @param normal the point's normal vector - * @param materialNumber the material number for this point - */ - public void appendPoint(Vector3f coordinates, Vector3f normal, int materialNumber) { - LOGGER.warning("Appending single point not yet supported!");//TODO - } - - /** - * This method adds a line to the mesh. - * @param v1 index of the 1'st vertex from the reference vertex table - * @param v2 index of the 2'nd vertex from the reference vertex table - * @param smooth indicates if this face should have smooth shading or flat shading - */ - public void appendEdge(int v1, int v2, boolean smooth) { - LOGGER.warning("Appending single line not yet supported!");//TODO - } - - /** - * This method adds a face to the mesh. - * @param v1 index of the 1'st vertex from the reference vertex table - * @param v2 index of the 2'nd vertex from the reference vertex table - * @param v3 index of the 3'rd vertex from the reference vertex table - * @param smooth indicates if this face should have smooth shading or flat shading - * @param materialNumber the material number for this face - * @param uvs a 3-element array of vertices UV coordinates - * @param quad indicates if the appended face is a part of a quad face (used for creating vertex colors buffer) - * @param faceIndex the face index (used for creating vertex colors buffer) - */ - public void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Vector2f[] uvs, boolean quad, int faceIndex) { - if(uvs != null && uvs.length != 3) { - throw new IllegalArgumentException("UV coordinates must be a 3-element array!"); - } - - //getting the required lists - List indexList = indexMap.get(materialNumber); + public MeshBuilder(Vector3f[][] verticesAndNormals, List verticesColors, boolean usesGeneratedTextures) { + this.verticesAndNormals = verticesAndNormals; + this.verticesColors = verticesColors; + this.usesGeneratedTextures = usesGeneratedTextures; + globalVertexReferenceMap = new HashMap>>(verticesAndNormals.length); + } + + /** + * This method adds a point to the mesh. + * @param coordinates + * the coordinates of the point + * @param normal + * the point's normal vector + * @param materialNumber + * the material number for this point + */ + public void appendPoint(Vector3f coordinates, Vector3f normal, int materialNumber) { + LOGGER.warning("Appending single point not yet supported!");// TODO + } + + /** + * This method adds a line to the mesh. + * @param v1 + * index of the 1'st vertex from the reference vertex table + * @param v2 + * index of the 2'nd vertex from the reference vertex table + * @param smooth + * indicates if this face should have smooth shading or flat shading + */ + public void appendEdge(int v1, int v2, boolean smooth) { + LOGGER.warning("Appending single line not yet supported!");// TODO + } + + /** + * This method adds a face to the mesh. + * @param v1 + * index of the 1'st vertex from the reference vertex table + * @param v2 + * index of the 2'nd vertex from the reference vertex table + * @param v3 + * index of the 3'rd vertex from the reference vertex table + * @param smooth + * indicates if this face should have smooth shading or flat shading + * @param materialNumber + * the material number for this face + * @param uvs + * a 3-element array of vertices UV coordinates + * @param quad + * indicates if the appended face is a part of a quad face (used for creating vertex colors buffer) + * @param faceIndex + * the face index (used for creating vertex colors buffer) + */ + public void appendFace(int v1, int v2, int v3, boolean smooth, int materialNumber, Vector2f[] uvs, boolean quad, int faceIndex) { + if (uvs != null && uvs.length != 3) { + throw new IllegalArgumentException("UV coordinates must be a 3-element array!"); + } + + // getting the required lists + List indexList = indexMap.get(materialNumber); if (indexList == null) { indexList = new ArrayList(); indexMap.put(materialNumber, indexList); @@ -102,187 +120,187 @@ import com.jme3.util.BufferUtils; } List vertexColorsList = vertexColorsMap != null ? vertexColorsMap.get(materialNumber) : null; int[] vertexColorIndex = new int[] { 0, 1, 2 }; - if(vertexColorsList == null && vertexColorsMap != null) { - vertexColorsList = new ArrayList(); - vertexColorsMap.put(materialNumber, vertexColorsList); + if (vertexColorsList == null && vertexColorsMap != null) { + vertexColorsList = new ArrayList(); + vertexColorsMap.put(materialNumber, vertexColorsList); } List normalList = normalMap.get(materialNumber); if (normalList == null) { - normalList = new ArrayList(); - normalMap.put(materialNumber, normalList); + normalList = new ArrayList(); + normalMap.put(materialNumber, normalList); } Map> vertexReferenceMap = globalVertexReferenceMap.get(materialNumber); - if(vertexReferenceMap == null) { - vertexReferenceMap = new HashMap>(); - globalVertexReferenceMap.put(materialNumber, vertexReferenceMap); + if (vertexReferenceMap == null) { + vertexReferenceMap = new HashMap>(); + globalVertexReferenceMap.put(materialNumber, vertexReferenceMap); } List uvCoordinatesList = null; - if(uvs != null) { - uvCoordinatesList = uvCoordinates.get(Integer.valueOf(materialNumber)); - if(uvCoordinatesList == null) { - uvCoordinatesList = new ArrayList(); - uvCoordinates.put(Integer.valueOf(materialNumber), uvCoordinatesList); - } + if (uvs != null) { + uvCoordinatesList = uvCoordinates.get(Integer.valueOf(materialNumber)); + if (uvCoordinatesList == null) { + uvCoordinatesList = new ArrayList(); + uvCoordinates.put(Integer.valueOf(materialNumber), uvCoordinatesList); + } } - + faceIndex *= 4; - if(quad) { - vertexColorIndex[1] = 2; - vertexColorIndex[2] = 3; + if (quad) { + vertexColorIndex[1] = 2; + vertexColorIndex[2] = 3; } - - //creating faces - Integer[] index = new Integer[] {v1, v2, v3}; - if(smooth && !usesGeneratedTextures) { - for (int i = 0; i < 3; ++i) { - if(!vertexReferenceMap.containsKey(index[i])) { - this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); - vertexList.add(verticesAndNormals[index[i]][0]); - if(verticesColors != null) { - vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); - } - normalList.add(verticesAndNormals[index[i]][1]); - if(uvCoordinatesList != null) { - uvsMap.put(vertexList.size(), uvs[i]); - uvCoordinatesList.add(uvs[i]); - } - index[i] = vertexList.size() - 1; - } else if(uvCoordinatesList != null) { - boolean vertexAlreadyUsed = false; - for(Integer vertexIndex : vertexReferenceMap.get(index[i])) { - if(uvs[i].equals(uvsMap.get(vertexIndex))) { - vertexAlreadyUsed = true; - index[i] = vertexIndex; - break; - } - } - if(!vertexAlreadyUsed) { - this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); - uvsMap.put(vertexList.size(), uvs[i]); - vertexList.add(verticesAndNormals[index[i]][0]); - if(verticesColors != null) { - vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); - } - normalList.add(verticesAndNormals[index[i]][1]); - uvCoordinatesList.add(uvs[i]); - index[i] = vertexList.size() - 1; - } - } else { - index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]); - } - indexList.add(index[i]); - } + + // creating faces + Integer[] index = new Integer[] { v1, v2, v3 }; + if (smooth && !usesGeneratedTextures) { + for (int i = 0; i < 3; ++i) { + if (!vertexReferenceMap.containsKey(index[i])) { + this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); + vertexList.add(verticesAndNormals[index[i]][0]); + if (verticesColors != null) { + vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); + } + normalList.add(verticesAndNormals[index[i]][1]); + if (uvCoordinatesList != null) { + uvsMap.put(vertexList.size(), uvs[i]); + uvCoordinatesList.add(uvs[i]); + } + index[i] = vertexList.size() - 1; + } else if (uvCoordinatesList != null) { + boolean vertexAlreadyUsed = false; + for (Integer vertexIndex : vertexReferenceMap.get(index[i])) { + if (uvs[i].equals(uvsMap.get(vertexIndex))) { + vertexAlreadyUsed = true; + index[i] = vertexIndex; + break; + } + } + if (!vertexAlreadyUsed) { + this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); + uvsMap.put(vertexList.size(), uvs[i]); + vertexList.add(verticesAndNormals[index[i]][0]); + if (verticesColors != null) { + vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); + } + normalList.add(verticesAndNormals[index[i]][1]); + uvCoordinatesList.add(uvs[i]); + index[i] = vertexList.size() - 1; + } + } else { + index[i] = vertexList.indexOf(verticesAndNormals[index[i]][0]); + } + indexList.add(index[i]); + } } else { - Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]); - for (int i = 0; i < 3; ++i) { - indexList.add(vertexList.size()); - this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); - if(uvCoordinatesList != null) { - uvCoordinatesList.add(uvs[i]); - uvsMap.put(vertexList.size(), uvs[i]); - } - vertexList.add(verticesAndNormals[index[i]][0]); - if(verticesColors != null) { - vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); - } - normalList.add(smooth ? verticesAndNormals[index[i]][1] : n); - } + Vector3f n = smooth ? null : FastMath.computeNormal(verticesAndNormals[v1][0], verticesAndNormals[v2][0], verticesAndNormals[v3][0]); + for (int i = 0; i < 3; ++i) { + indexList.add(vertexList.size()); + this.appendVertexReference(index[i], vertexList.size(), vertexReferenceMap); + if (uvCoordinatesList != null) { + uvCoordinatesList.add(uvs[i]); + uvsMap.put(vertexList.size(), uvs[i]); + } + vertexList.add(verticesAndNormals[index[i]][0]); + if (verticesColors != null) { + vertexColorsList.add(verticesColors.get(faceIndex + vertexColorIndex[i])); + } + normalList.add(smooth ? verticesAndNormals[index[i]][1] : n); + } } - } - - /** - * @return a map that maps vertex index from reference array to its indices in the result list - */ - public Map>> getVertexReferenceMap() { - return globalVertexReferenceMap; - } - - /** - * @param materialNumber - * the material index - * @return result vertices array - */ - public Vector3f[] getVertices(int materialNumber) { - return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]); - } - - /** - * @param materialNumber - * the material index - * @return the amount of result vertices - */ - public int getVerticesAmount(int materialNumber) { - return vertexMap.get(materialNumber).size(); - } - - /** - * @param materialNumber - * the material index - * @return normals result array - */ - public Vector3f[] getNormals(int materialNumber) { - return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]); - } - - /** - * @param materialNumber - * the material index - * @return the vertices colors buffer or null if no vertex colors is set - */ - public ByteBuffer getVertexColorsBuffer(int materialNumber) { - ByteBuffer result = null; - if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) { - List data = vertexColorsMap.get(materialNumber); - result = BufferUtils.createByteBuffer(4 * data.size()); - for (byte[] v : data) { - if (v != null) { - result.put(v[0]).put(v[1]).put(v[2]).put(v[3]); - } else { - result.put((byte)0).put((byte)0).put((byte)0).put((byte)0); - } - } - result.flip(); - } - return result; - } - - /** - * @return a map between material number and the mesh part vertices indices - */ - public Map> getMeshesMap() { - return indexMap; - } - - /** - * @return the amount of meshes the source mesh was split into (depends on the applied materials count) - */ - public int getMeshesPartAmount() { - return indexMap.size(); - } - - /** - * @param materialNumber - * the material number that is appied to the mesh - * @return UV coordinates of vertices that belong to the required mesh part - */ - public List getUVCoordinates(int materialNumber) { - return uvCoordinates.get(materialNumber); - } - - /** - * @return indicates if the mesh has UV coordinates - */ - public boolean hasUVCoordinates() { - return uvCoordinates.size() > 0; - } - - /** - * @return true if the mesh has no vertices and false otherwise - */ - public boolean isEmpty() { - return vertexMap.size() == 0; - } - + } + + /** + * @return a map that maps vertex index from reference array to its indices in the result list + */ + public Map>> getVertexReferenceMap() { + return globalVertexReferenceMap; + } + + /** + * @param materialNumber + * the material index + * @return result vertices array + */ + public Vector3f[] getVertices(int materialNumber) { + return vertexMap.get(materialNumber).toArray(new Vector3f[vertexMap.get(materialNumber).size()]); + } + + /** + * @param materialNumber + * the material index + * @return the amount of result vertices + */ + public int getVerticesAmount(int materialNumber) { + return vertexMap.get(materialNumber).size(); + } + + /** + * @param materialNumber + * the material index + * @return normals result array + */ + public Vector3f[] getNormals(int materialNumber) { + return normalMap.get(materialNumber).toArray(new Vector3f[normalMap.get(materialNumber).size()]); + } + + /** + * @param materialNumber + * the material index + * @return the vertices colors buffer or null if no vertex colors is set + */ + public ByteBuffer getVertexColorsBuffer(int materialNumber) { + ByteBuffer result = null; + if (verticesColors != null && vertexColorsMap.get(materialNumber) != null) { + List data = vertexColorsMap.get(materialNumber); + result = BufferUtils.createByteBuffer(4 * data.size()); + for (byte[] v : data) { + if (v != null) { + result.put(v[0]).put(v[1]).put(v[2]).put(v[3]); + } else { + result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0); + } + } + result.flip(); + } + return result; + } + + /** + * @return a map between material number and the mesh part vertices indices + */ + public Map> getMeshesMap() { + return indexMap; + } + + /** + * @return the amount of meshes the source mesh was split into (depends on the applied materials count) + */ + public int getMeshesPartAmount() { + return indexMap.size(); + } + + /** + * @param materialNumber + * the material number that is appied to the mesh + * @return UV coordinates of vertices that belong to the required mesh part + */ + public List getUVCoordinates(int materialNumber) { + return uvCoordinates.get(materialNumber); + } + + /** + * @return indicates if the mesh has UV coordinates + */ + public boolean hasUVCoordinates() { + return uvCoordinates.size() > 0; + } + + /** + * @return true if the mesh has no vertices and false otherwise + */ + public boolean isEmpty() { + return vertexMap.size() == 0; + } + /** * This method fills the vertex reference map. The vertices are loaded once and referenced many times in the model. This map is created * to tell where the basic vertices are referenced in the result vertex lists. The key of the map is the basic vertex index, and its key diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshContext.java index b62173a21..8e373be34 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshContext.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshContext.java @@ -14,131 +14,135 @@ import com.jme3.scene.VertexBuffer; * @author Marcin Roguski (Kaelthas) */ public class MeshContext { - /** A map between material index and the geometry. */ - private Map geometries = new HashMap(); - /** The vertex reference map. */ - private Map>> vertexReferenceMap; - /** The UV-coordinates for each of the geometries. */ - private Map uvCoordinates = new HashMap(); - /** Bind buffer for vertices is stored here and applied when required. */ - private Map bindPoseBuffer = new HashMap(); - /** Bind buffer for normals is stored here and applied when required. */ - private Map bindNormalBuffer = new HashMap(); + /** A map between material index and the geometry. */ + private Map geometries = new HashMap(); + /** The vertex reference map. */ + private Map>> vertexReferenceMap; + /** The UV-coordinates for each of the geometries. */ + private Map uvCoordinates = new HashMap(); + /** Bind buffer for vertices is stored here and applied when required. */ + private Map bindPoseBuffer = new HashMap(); + /** Bind buffer for normals is stored here and applied when required. */ + private Map bindNormalBuffer = new HashMap(); - /** - * Adds a geometry for the specified material index. - * @param materialIndex the material index - * @param geometry the geometry - */ - public void putGeometry(Integer materialIndex, Geometry geometry) { - geometries.put(materialIndex, geometry); - } - - /** - * @param materialIndex the material index - * @return vertices amount that is used by mesh with the specified material - */ - public int getVertexCount(int materialIndex) { - return geometries.get(materialIndex).getVertexCount(); - } - - /** - * Returns material index for the geometry. - * @param geometry the geometry - * @return material index - * @throws IllegalStateException this exception is thrown when no material is found for the specified geometry - */ - public int getMaterialIndex(Geometry geometry) { - for(Entry entry : geometries.entrySet()) { - if(entry.getValue().equals(geometry)) { - return entry.getKey(); - } - } - throw new IllegalStateException("Cannot find material index for the given geometry: " + geometry); - } - - /** - * This method returns the vertex reference map. - * - * @return the vertex reference map - */ - public Map> getVertexReferenceMap(int materialIndex) { - return vertexReferenceMap.get(materialIndex); - } + /** + * Adds a geometry for the specified material index. + * @param materialIndex + * the material index + * @param geometry + * the geometry + */ + public void putGeometry(Integer materialIndex, Geometry geometry) { + geometries.put(materialIndex, geometry); + } - /** - * This method sets the vertex reference map. - * - * @param vertexReferenceMap - * the vertex reference map - */ - public void setVertexReferenceMap(Map>> vertexReferenceMap) { - this.vertexReferenceMap = vertexReferenceMap; - } + /** + * @param materialIndex + * the material index + * @return vertices amount that is used by mesh with the specified material + */ + public int getVertexCount(int materialIndex) { + return geometries.get(materialIndex).getVertexCount(); + } - /** - * This method adds the mesh's UV-coordinates. - * - * @param geometry - * the mesh that has the UV-coordinates - * @param vertexBuffer - * the mesh's UV-coordinates - */ - public void addUVCoordinates(Geometry geometry, VertexBuffer vertexBuffer) { - uvCoordinates.put(geometry, vertexBuffer); - } + /** + * Returns material index for the geometry. + * @param geometry + * the geometry + * @return material index + * @throws IllegalStateException + * this exception is thrown when no material is found for the specified geometry + */ + public int getMaterialIndex(Geometry geometry) { + for (Entry entry : geometries.entrySet()) { + if (entry.getValue().equals(geometry)) { + return entry.getKey(); + } + } + throw new IllegalStateException("Cannot find material index for the given geometry: " + geometry); + } - /** - * This method returns the mesh's UV-coordinates. - * - * @param geometry - * the mesh - * @return the mesh's UV-coordinates - */ - public VertexBuffer getUVCoordinates(Geometry geometry) { - return uvCoordinates.get(geometry); - } + /** + * This method returns the vertex reference map. + * + * @return the vertex reference map + */ + public Map> getVertexReferenceMap(int materialIndex) { + return vertexReferenceMap.get(materialIndex); + } - /** - * This method sets the bind buffer for vertices. - * - * @param materialIndex - * the index of the mesh's material - * @param bindNormalBuffer - * the bind buffer for vertices - */ - public void setBindNormalBuffer(int materialIndex, - VertexBuffer bindNormalBuffer) { - this.bindNormalBuffer.put(materialIndex, bindNormalBuffer); - } + /** + * This method sets the vertex reference map. + * + * @param vertexReferenceMap + * the vertex reference map + */ + public void setVertexReferenceMap(Map>> vertexReferenceMap) { + this.vertexReferenceMap = vertexReferenceMap; + } - /** - * @param materialIndex - * the index of the mesh's material - * @return the bind buffer for vertices - */ - public VertexBuffer getBindNormalBuffer(int materialIndex) { - return bindNormalBuffer.get(materialIndex); - } + /** + * This method adds the mesh's UV-coordinates. + * + * @param geometry + * the mesh that has the UV-coordinates + * @param vertexBuffer + * the mesh's UV-coordinates + */ + public void addUVCoordinates(Geometry geometry, VertexBuffer vertexBuffer) { + uvCoordinates.put(geometry, vertexBuffer); + } - /** - * This method sets the bind buffer for normals. - * - * @param materialIndex - * the index of the mesh's material - * @param bindNormalBuffer - * the bind buffer for normals - */ - public void setBindPoseBuffer(int materialIndex, VertexBuffer bindPoseBuffer) { - this.bindPoseBuffer.put(materialIndex, bindPoseBuffer); - } + /** + * This method returns the mesh's UV-coordinates. + * + * @param geometry + * the mesh + * @return the mesh's UV-coordinates + */ + public VertexBuffer getUVCoordinates(Geometry geometry) { + return uvCoordinates.get(geometry); + } - /** - * @param materialIndex - * the index of the mesh's material - * @return the bind buffer for normals - */ - public VertexBuffer getBindPoseBuffer(int materialIndex) { - return bindPoseBuffer.get(materialIndex); - } + /** + * This method sets the bind buffer for vertices. + * + * @param materialIndex + * the index of the mesh's material + * @param bindNormalBuffer + * the bind buffer for vertices + */ + public void setBindNormalBuffer(int materialIndex, VertexBuffer bindNormalBuffer) { + this.bindNormalBuffer.put(materialIndex, bindNormalBuffer); + } + + /** + * @param materialIndex + * the index of the mesh's material + * @return the bind buffer for vertices + */ + public VertexBuffer getBindNormalBuffer(int materialIndex) { + return bindNormalBuffer.get(materialIndex); + } + + /** + * This method sets the bind buffer for normals. + * + * @param materialIndex + * the index of the mesh's material + * @param bindNormalBuffer + * the bind buffer for normals + */ + public void setBindPoseBuffer(int materialIndex, VertexBuffer bindPoseBuffer) { + this.bindPoseBuffer.put(materialIndex, bindPoseBuffer); + } + + /** + * @param materialIndex + * the index of the mesh's material + * @return the bind buffer for normals + */ + public VertexBuffer getBindPoseBuffer(int materialIndex) { + return bindPoseBuffer.get(materialIndex); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java index 7dbdf72e8..75a2c9ac8 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/meshes/MeshHelper.java @@ -63,8 +63,8 @@ import com.jme3.util.BufferUtils; * @author Marcin Roguski (Kaelthas) */ public class MeshHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(MeshHelper.class.getName()); - + private static final Logger LOGGER = Logger.getLogger(MeshHelper.class.getName()); + /** * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender * versions. @@ -72,10 +72,10 @@ public class MeshHelper extends AbstractBlenderHelper { * @param blenderVersion * the version read from the blend file * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not + * a variable that indicates if the Y asxis is the UP axis or not */ public MeshHelper(String blenderVersion, boolean fixUpAxis) { - super(blenderVersion,fixUpAxis); + super(blenderVersion, fixUpAxis); } /** @@ -107,26 +107,26 @@ public class MeshHelper extends AbstractBlenderHelper { if ((blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.MATERIALS) != 0) { materials = materialHelper.getMaterials(structure, blenderContext); } - + // reading vertices and their colors Vector3f[][] verticesAndNormals = this.getVerticesAndNormals(structure, blenderContext); List verticesColors = this.getVerticesColors(structure, blenderContext); - + MeshBuilder meshBuilder = new MeshBuilder(verticesAndNormals, verticesColors, this.areGeneratedTexturesPresent(materials)); - if(this.isBMeshCompatible(structure)) { - this.readBMesh(meshBuilder, structure, blenderContext); + if (this.isBMeshCompatible(structure)) { + this.readBMesh(meshBuilder, structure, blenderContext); } else { - this.readTraditionalFaces(meshBuilder, structure, blenderContext); + this.readTraditionalFaces(meshBuilder, structure, blenderContext); } - if(meshBuilder.isEmpty()) { - geometries = new ArrayList(0); - blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries); + if (meshBuilder.isEmpty()) { + geometries = new ArrayList(0); + blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries); blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext); return geometries; } - + meshContext.setVertexReferenceMap(meshBuilder.getVertexReferenceMap()); // reading vertices groups (from the parent) @@ -142,32 +142,32 @@ public class MeshHelper extends AbstractBlenderHelper { // creating the result meshes geometries = new ArrayList(meshBuilder.getMeshesPartAmount()); - //reading custom properties + // reading custom properties Properties properties = this.loadProperties(structure, blenderContext); // generating meshes for (Entry> meshEntry : meshBuilder.getMeshesMap().entrySet()) { - int materialIndex = meshEntry.getKey(); - //key is the material index (or -1 if the material has no texture) - //value is a list of vertex indices + int materialIndex = meshEntry.getKey(); + // key is the material index (or -1 if the material has no texture) + // value is a list of vertex indices Mesh mesh = new Mesh(); - + // creating vertices indices for this mesh List indexList = meshEntry.getValue(); - if(meshBuilder.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) { - short[] indices = new short[indexList.size()]; + if (meshBuilder.getVerticesAmount(materialIndex) <= Short.MAX_VALUE) { + short[] indices = new short[indexList.size()]; for (int i = 0; i < indexList.size(); ++i) { indices[i] = indexList.get(i).shortValue(); } mesh.setBuffer(Type.Index, 1, indices); } else { - int[] indices = new int[indexList.size()]; + int[] indices = new int[indexList.size()]; for (int i = 0; i < indexList.size(); ++i) { indices[i] = indexList.get(i).intValue(); } mesh.setBuffer(Type.Index, 1, indices); } - + VertexBuffer verticesBuffer = new VertexBuffer(Type.Position); verticesBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.getVertices(materialIndex))); @@ -181,9 +181,9 @@ public class MeshHelper extends AbstractBlenderHelper { // initial normals position (used with animation) VertexBuffer normalsBind = new VertexBuffer(Type.BindPoseNormal); normalsBind.setupData(Usage.CpuOnly, 3, Format.Float, BufferUtils.createFloatBuffer(meshBuilder.getNormals(materialIndex))); - + mesh.setBuffer(verticesBuffer); - meshContext.setBindPoseBuffer(materialIndex, verticesBind);//this is stored in the context and applied when needed (when animation is applied to the mesh) + meshContext.setBindPoseBuffer(materialIndex, verticesBind);// this is stored in the context and applied when needed (when animation is applied to the mesh) // setting vertices colors if (verticesColors != null) { @@ -193,227 +193,225 @@ public class MeshHelper extends AbstractBlenderHelper { // setting faces' normals mesh.setBuffer(normalsBuffer); - meshContext.setBindNormalBuffer(materialIndex, normalsBind);//this is stored in the context and applied when needed (when animation is applied to the mesh) + meshContext.setBindNormalBuffer(materialIndex, normalsBind);// this is stored in the context and applied when needed (when animation is applied to the mesh) // creating the result Geometry geometry = new Geometry(name + (geometries.size() + 1), mesh); if (properties != null && properties.getValue() != null) { - this.applyProperties(geometry, properties); + this.applyProperties(geometry, properties); } geometries.add(geometry); meshContext.putGeometry(materialIndex, geometry); } - - //store the data in blender context before applying the material + + // store the data in blender context before applying the material blenderContext.addLoadedFeatures(structure.getOldMemoryAddress(), structure.getName(), structure, geometries); blenderContext.setMeshContext(structure.getOldMemoryAddress(), meshContext); - - //apply materials only when all geometries are in place - if(materials != null) { - for(Geometry geometry : geometries) { - int materialNumber = meshContext.getMaterialIndex(geometry); - if(materials[materialNumber] != null) { - List uvCoordinates = meshBuilder.getUVCoordinates(materialNumber); - MaterialContext materialContext = materials[materialNumber]; - materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), uvCoordinates, blenderContext); + + // apply materials only when all geometries are in place + if (materials != null) { + for (Geometry geometry : geometries) { + int materialNumber = meshContext.getMaterialIndex(geometry); + if (materials[materialNumber] != null) { + List uvCoordinates = meshBuilder.getUVCoordinates(materialNumber); + MaterialContext materialContext = materials[materialNumber]; + materialContext.applyMaterial(geometry, structure.getOldMemoryAddress(), uvCoordinates, blenderContext); } else { - geometry.setMaterial(blenderContext.getDefaultMaterial()); - LOGGER.warning("The importer came accross mesh that points to a null material. Default material is used to prevent loader from crashing, " + - "but the model might look not the way it should. Sometimes blender does not assign materials properly. " + - "Enter the edit mode and assign materials once more to your faces."); + geometry.setMaterial(blenderContext.getDefaultMaterial()); + LOGGER.warning("The importer came accross mesh that points to a null material. Default material is used to prevent loader from crashing, " + "but the model might look not the way it should. Sometimes blender does not assign materials properly. " + "Enter the edit mode and assign materials once more to your faces."); } - } + } } else { - //add UV coordinates if they are defined even if the material is not applied to the model - VertexBuffer uvCoordsBuffer = null; - if(meshBuilder.hasUVCoordinates()) { - List uvs = meshBuilder.getUVCoordinates(0); - uvCoordsBuffer = new VertexBuffer(Type.TexCoord); - uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()]))); - } - - for(Geometry geometry : geometries) { - geometry.setMaterial(blenderContext.getDefaultMaterial()); - if(uvCoordsBuffer != null) { - geometry.getMesh().setBuffer(uvCoordsBuffer); - } - } + // add UV coordinates if they are defined even if the material is not applied to the model + VertexBuffer uvCoordsBuffer = null; + if (meshBuilder.hasUVCoordinates()) { + List uvs = meshBuilder.getUVCoordinates(0); + uvCoordsBuffer = new VertexBuffer(Type.TexCoord); + uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()]))); + } + + for (Geometry geometry : geometries) { + geometry.setMaterial(blenderContext.getDefaultMaterial()); + if (uvCoordsBuffer != null) { + geometry.getMesh().setBuffer(uvCoordsBuffer); + } + } } - + return geometries; } - - /** - * Tells if the given mesh structure supports BMesh. - * - * @param meshStructure - * the mesh structure - * @return true if BMesh is supported and false otherwise - */ + + /** + * Tells if the given mesh structure supports BMesh. + * + * @param meshStructure + * the mesh structure + * @return true if BMesh is supported and false otherwise + */ private boolean isBMeshCompatible(Structure meshStructure) { - Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); - Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); - return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull(); + Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); + Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); + return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull(); + } + + /** + * This method reads the mesh from the new BMesh system. + * + * @param meshBuilder + * the mesh builder + * @param meshStructure + * the mesh structure + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ + @SuppressWarnings("unchecked") + private void readBMesh(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { + Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); + Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); + Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); + Pointer pMLoopUV = (Pointer) meshStructure.getFieldValue("mloopuv"); + Vector2f[] uvCoordinatesForFace = new Vector2f[3]; + + if (pMPoly.isNotNull() && pMLoop.isNotNull() && pMEdge.isNotNull()) { + int faceIndex = 0; + List polys = pMPoly.fetchData(blenderContext.getInputStream()); + List loops = pMLoop.fetchData(blenderContext.getInputStream()); + List loopuvs = pMLoopUV.isNotNull() ? pMLoopUV.fetchData(blenderContext.getInputStream()) : null; + for (Structure poly : polys) { + int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue(); + int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue(); + int totLoop = ((Number) poly.getFieldValue("totloop")).intValue(); + boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00; + int[] vertexIndexes = new int[totLoop]; + Vector2f[] uvs = loopuvs != null ? new Vector2f[totLoop] : null; + + for (int i = loopStart; i < loopStart + totLoop; ++i) { + vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue(); + if (uvs != null) { + DynamicArray loopUVS = (DynamicArray) loopuvs.get(i).getFieldValue("uv"); + uvs[i - loopStart] = new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue()); + } + } + + int i = 0; + while (i < totLoop - 2) { + int v1 = vertexIndexes[0]; + int v2 = vertexIndexes[i + 1]; + int v3 = vertexIndexes[i + 2]; + + if (uvs != null) {// uvs always must be added wheater we + // have texture or not + uvCoordinatesForFace[0] = uvs[0]; + uvCoordinatesForFace[1] = uvs[i + 1]; + uvCoordinatesForFace[2] = uvs[i + 2]; + } + + meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, faceIndex); + + ++i; + } + ++faceIndex; + } + } } - - /** - * This method reads the mesh from the new BMesh system. - * - * @param meshBuilder - * the mesh builder - * @param meshStructure - * the mesh structure - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when there are problems with the - * blender file - */ - @SuppressWarnings("unchecked") - private void readBMesh(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { - Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); - Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); - Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); - Pointer pMLoopUV = (Pointer) meshStructure.getFieldValue("mloopuv"); - Vector2f[] uvCoordinatesForFace = new Vector2f[3]; - - if (pMPoly.isNotNull() && pMLoop.isNotNull() && pMEdge.isNotNull()) { - int faceIndex = 0; - List polys = pMPoly.fetchData(blenderContext.getInputStream()); - List loops = pMLoop.fetchData(blenderContext.getInputStream()); - List loopuvs = pMLoopUV.isNotNull() ? pMLoopUV.fetchData(blenderContext.getInputStream()) : null; - for (Structure poly : polys) { - int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue(); - int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue(); - int totLoop = ((Number) poly.getFieldValue("totloop")).intValue(); - boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00; - int[] vertexIndexes = new int[totLoop]; - Vector2f[] uvs = loopuvs != null ? new Vector2f[totLoop] : null; - - for (int i = loopStart; i < loopStart + totLoop; ++i) { - vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue(); - if (uvs != null) { - DynamicArray loopUVS = (DynamicArray) loopuvs.get(i).getFieldValue("uv"); - uvs[i - loopStart] = new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue()); - } - } - - int i = 0; - while (i < totLoop - 2) { - int v1 = vertexIndexes[0]; - int v2 = vertexIndexes[i + 1]; - int v3 = vertexIndexes[i + 2]; - - if (uvs != null) {// uvs always must be added wheater we - // have texture or not - uvCoordinatesForFace[0] = uvs[0]; - uvCoordinatesForFace[1] = uvs[i + 1]; - uvCoordinatesForFace[2] = uvs[i + 2]; - } - - meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, faceIndex); - - ++i; - } - ++faceIndex; - } - } - } - - /** - * This method reads the mesh from traditional triangle/quad storing - * structures. - * - * @param meshBuilder - * the mesh builder - * @param meshStructure - * the mesh structure - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when there are problems with the - * blender file - */ - @SuppressWarnings("unchecked") - private void readTraditionalFaces(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { - Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface"); - List mFaces = pMFace.isNotNull() ? pMFace.fetchData(blenderContext.getInputStream()) : null; - if (mFaces != null && mFaces.size() > 0) { - Pointer pMTFace = (Pointer) meshStructure.getFieldValue("mtface"); - List mtFaces = null; - - if (pMTFace.isNotNull()) { - mtFaces = pMTFace.fetchData(blenderContext.getInputStream()); - int facesAmount = ((Number) meshStructure.getFieldValue("totface")).intValue(); - if (mtFaces.size() != facesAmount) { - throw new BlenderFileException("The amount of faces uv coordinates is not equal to faces amount!"); - } - } - - // indicates if the material with the specified number should have a - // texture attached - Vector2f[] uvCoordinatesForFace = new Vector2f[3]; - for (int i = 0; i < mFaces.size(); ++i) { - Structure mFace = mFaces.get(i); - int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue(); - boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00; - DynamicArray uvs = null; - - if (mtFaces != null) { - Structure mtFace = mtFaces.get(i); - // uvs always must be added wheater we have texture or not - uvs = (DynamicArray) mtFace.getFieldValue("uv"); - uvCoordinatesForFace[0] = new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()); - uvCoordinatesForFace[1] = new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue()); - uvCoordinatesForFace[2] = new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()); - } - - int v1 = ((Number) mFace.getFieldValue("v1")).intValue(); - int v2 = ((Number) mFace.getFieldValue("v2")).intValue(); - int v3 = ((Number) mFace.getFieldValue("v3")).intValue(); - int v4 = ((Number) mFace.getFieldValue("v4")).intValue(); - - meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, i); - if (v4 > 0) { - if (uvs != null) { - uvCoordinatesForFace[0] = new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()); - uvCoordinatesForFace[1] = new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()); - uvCoordinatesForFace[2] = new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue()); - } - meshBuilder.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, true, i); - } - } - } else { - Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); - List mEdges = pMEdge.isNotNull() ? pMEdge.fetchData(blenderContext.getInputStream()) : null; - if (mEdges != null && mEdges.size() > 0) { - for (int i = 0; i < mEdges.size(); ++i) { - Structure mEdge = mEdges.get(i); - boolean smooth = (((Number) mEdge.getFieldValue("flag")).byteValue() & 0x01) != 0x00; - - int v1 = ((Number) mEdge.getFieldValue("v1")).intValue(); - int v2 = ((Number) mEdge.getFieldValue("v2")).intValue(); - - meshBuilder.appendEdge(v1, v2, smooth); - } - } - } - } /** - * @return true if the material has at least one generated component and false otherwise - */ + * This method reads the mesh from traditional triangle/quad storing + * structures. + * + * @param meshBuilder + * the mesh builder + * @param meshStructure + * the mesh structure + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when there are problems with the + * blender file + */ + @SuppressWarnings("unchecked") + private void readTraditionalFaces(MeshBuilder meshBuilder, Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { + Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface"); + List mFaces = pMFace.isNotNull() ? pMFace.fetchData(blenderContext.getInputStream()) : null; + if (mFaces != null && mFaces.size() > 0) { + Pointer pMTFace = (Pointer) meshStructure.getFieldValue("mtface"); + List mtFaces = null; + + if (pMTFace.isNotNull()) { + mtFaces = pMTFace.fetchData(blenderContext.getInputStream()); + int facesAmount = ((Number) meshStructure.getFieldValue("totface")).intValue(); + if (mtFaces.size() != facesAmount) { + throw new BlenderFileException("The amount of faces uv coordinates is not equal to faces amount!"); + } + } + + // indicates if the material with the specified number should have a + // texture attached + Vector2f[] uvCoordinatesForFace = new Vector2f[3]; + for (int i = 0; i < mFaces.size(); ++i) { + Structure mFace = mFaces.get(i); + int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue(); + boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00; + DynamicArray uvs = null; + + if (mtFaces != null) { + Structure mtFace = mtFaces.get(i); + // uvs always must be added wheater we have texture or not + uvs = (DynamicArray) mtFace.getFieldValue("uv"); + uvCoordinatesForFace[0] = new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()); + uvCoordinatesForFace[1] = new Vector2f(uvs.get(1, 0).floatValue(), uvs.get(1, 1).floatValue()); + uvCoordinatesForFace[2] = new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()); + } + + int v1 = ((Number) mFace.getFieldValue("v1")).intValue(); + int v2 = ((Number) mFace.getFieldValue("v2")).intValue(); + int v3 = ((Number) mFace.getFieldValue("v3")).intValue(); + int v4 = ((Number) mFace.getFieldValue("v4")).intValue(); + + meshBuilder.appendFace(v1, v2, v3, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, false, i); + if (v4 > 0) { + if (uvs != null) { + uvCoordinatesForFace[0] = new Vector2f(uvs.get(0, 0).floatValue(), uvs.get(0, 1).floatValue()); + uvCoordinatesForFace[1] = new Vector2f(uvs.get(2, 0).floatValue(), uvs.get(2, 1).floatValue()); + uvCoordinatesForFace[2] = new Vector2f(uvs.get(3, 0).floatValue(), uvs.get(3, 1).floatValue()); + } + meshBuilder.appendFace(v1, v3, v4, smooth, materialNumber, uvs == null ? null : uvCoordinatesForFace, true, i); + } + } + } else { + Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); + List mEdges = pMEdge.isNotNull() ? pMEdge.fetchData(blenderContext.getInputStream()) : null; + if (mEdges != null && mEdges.size() > 0) { + for (int i = 0; i < mEdges.size(); ++i) { + Structure mEdge = mEdges.get(i); + boolean smooth = (((Number) mEdge.getFieldValue("flag")).byteValue() & 0x01) != 0x00; + + int v1 = ((Number) mEdge.getFieldValue("v1")).intValue(); + int v2 = ((Number) mEdge.getFieldValue("v2")).intValue(); + + meshBuilder.appendEdge(v1, v2, smooth); + } + } + } + } + + /** + * @return true if the material has at least one generated component and false otherwise + */ private boolean areGeneratedTexturesPresent(MaterialContext[] materials) { - if(materials != null) { - for(MaterialContext material : materials) { - if(material != null && material.hasGeneratedTextures()) { - return true; - } - } - } - return false; + if (materials != null) { + for (MaterialContext material : materials) { + if (material != null && material.hasGeneratedTextures()) { + return true; + } + } + } + return false; } - + /** * This method returns the vertices colors. Each vertex is stored in byte[4] array. * @@ -433,11 +431,11 @@ public class MeshHelper extends AbstractBlenderHelper { verticesColors = new ArrayList(); mCol = pMCol.fetchData(blenderContext.getInputStream()); for (Structure color : mCol) { - byte r = ((Number)color.getFieldValue("r")).byteValue(); - byte g = ((Number)color.getFieldValue("g")).byteValue(); - byte b = ((Number)color.getFieldValue("b")).byteValue(); - byte a = ((Number)color.getFieldValue("a")).byteValue(); - verticesColors.add(new byte[]{b, g, r, a}); + byte r = ((Number) color.getFieldValue("r")).byteValue(); + byte g = ((Number) color.getFieldValue("g")).byteValue(); + byte b = ((Number) color.getFieldValue("b")).byteValue(); + byte a = ((Number) color.getFieldValue("a")).byteValue(); + verticesColors.add(new byte[] { b, g, r, a }); } } return verticesColors; @@ -464,21 +462,21 @@ public class MeshHelper extends AbstractBlenderHelper { Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert"); List mVerts = pMVert.fetchData(blenderContext.getInputStream()); - if(this.fixUpAxis) { - for (int i = 0; i < count; ++i) { + if (this.fixUpAxis) { + for (int i = 0; i < count; ++i) { DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue()); - + DynamicArray normals = (DynamicArray) mVerts.get(i).getFieldValue("no"); - result[i][1] = new Vector3f(normals.get(0).shortValue()/32767.0f, normals.get(2).shortValue()/32767.0f, -normals.get(1).shortValue()/32767.0f); + result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f, -normals.get(1).shortValue() / 32767.0f); } } else { - for (int i = 0; i < count; ++i) { + for (int i = 0; i < count; ++i) { DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); result[i][0] = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue()); - + DynamicArray normals = (DynamicArray) mVerts.get(i).getFieldValue("no"); - result[i][1] = new Vector3f(normals.get(0).shortValue()/32767.0f, normals.get(1).shortValue()/32767.0f, normals.get(2).shortValue()/32767.0f); + result[i][1] = new Vector3f(normals.get(0).shortValue() / 32767.0f, normals.get(1).shortValue() / 32767.0f, normals.get(2).shortValue() / 32767.0f); } } return result; diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java index 6dfae7d0c..8b2e8611b 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java @@ -43,378 +43,375 @@ import com.jme3.util.BufferUtils; * @author Marcin Roguski (Kaelthas) */ /* package */class ArmatureModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName()); - private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4;//JME limitation - - private Skeleton skeleton; - private Structure objectStructure; - private Structure meshStructure; - - /** Loaded animation data. */ - private AnimData animData; - /** Old memory address of the mesh that will have the skeleton applied. */ - private Long meshOMA; - - /** - * This constructor reads animation data from the object structore. The - * stored data is the AnimData and additional data is armature's OMA. - * - * @param objectStructure - * the structure of the object - * @param modifierStructure - * the structure of the modifier - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { - Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0); - Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices - - // if pDvert==null then there are not vertex groups and no need to load - // skeleton (untill bone envelopes are supported) - if (this.validate(modifierStructure, blenderContext) && pDvert.isNotNull()) { - Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object"); - if (pArmatureObject.isNotNull()) { - ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); - - Structure armatureObject = pArmatureObject.fetchData(blenderContext.getInputStream()).get(0); - - // load skeleton - Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0); - - Structure pose = ((Pointer) armatureObject.getFieldValue("pose")).fetchData(blenderContext.getInputStream()).get(0); - List chanbase = ((Structure) pose.getFieldValue("chanbase")).evaluateListBase(blenderContext); - - Map bonesPoseChannels = new HashMap(chanbase.size()); - for (Structure poseChannel : chanbase) { - Pointer pBone = (Pointer) poseChannel.getFieldValue("bone"); - bonesPoseChannels.put(pBone.getOldMemoryAddress(), poseChannel); - } - - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Matrix4f armatureObjectMatrix = objectHelper.getMatrix(armatureObject, "obmat", true); - Matrix4f inverseMeshObjectMatrix = objectHelper.getMatrix(objectStructure, "obmat", true).invertLocal(); - Matrix4f objectToArmatureTransformation = armatureObjectMatrix.multLocal(inverseMeshObjectMatrix); - - List bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(blenderContext); - List bonesList = new ArrayList(); - for (int i = 0; i < bonebase.size(); ++i) { - armatureHelper.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectToArmatureTransformation, bonesPoseChannels, blenderContext); - } - bonesList.add(0, new Bone("")); - Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]); - skeleton = new Skeleton(bones); - blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton); - this.objectStructure = objectStructure; - this.meshStructure = meshStructure; - - // read mesh indexes - this.meshOMA = meshStructure.getOldMemoryAddress(); - - // read animations - ArrayList animations = new ArrayList(); - List actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); - if (actionHeaders != null) {// it may happen that the model has armature with no actions - for (FileBlockHeader header : actionHeaders) { - Structure actionStructure = header.getStructure(blenderContext); - String actionName = actionStructure.getName(); - - BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext); - if(tracks != null && tracks.length > 0) { - // determining the animation time - float maximumTrackLength = 0; - for (BoneTrack track : tracks) { - float length = track.getLength(); - if (length > maximumTrackLength) { - maximumTrackLength = length; - } - } - - Animation boneAnimation = new Animation(actionName, maximumTrackLength); - boneAnimation.setTracks(tracks); - animations.add(boneAnimation); - } - } - } - //fetching action defined in object - Pointer pAction = (Pointer) objectStructure.getFieldValue("action"); - if (pAction.isNotNull()) { - Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0); - String actionName = actionStructure.getName(); - - BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext); - if(tracks != null && tracks.length > 0) { - // determining the animation time - float maximumTrackLength = 0; - for (BoneTrack track : tracks) { - float length = track.getLength(); - if (length > maximumTrackLength) { - maximumTrackLength = length; - } - } - - Animation boneAnimation = new Animation(actionName, maximumTrackLength); - boneAnimation.setTracks(tracks); - animations.add(boneAnimation); - } - } - - animData = new AnimData(skeleton, animations); - - // store the animation data for each bone - for (Bone bone : bones) { - Long boneOma = armatureHelper.getBoneOMA(bone); - if (boneOma != null) { - blenderContext.setAnimData(boneOma, animData); - } - } - } else { - modifying = false; - } - } - } - - @Override - @SuppressWarnings("unchecked") - public Node apply(Node node, BlenderContext blenderContext) { - if (invalid) { - LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); - }// if invalid, animData will be null - if (animData == null || skeleton == null) { - return node; - } - - // setting weights for bones - List geomList = (List) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE); - MeshContext meshContext = blenderContext.getMeshContext(meshOMA); - int[] bonesGroups = new int[] { 0 }; - for (Geometry geom : geomList) { - int materialIndex = meshContext.getMaterialIndex(geom); - Mesh mesh = geom.getMesh(); - - try { - VertexBuffer[] buffers = this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, materialIndex, bonesGroups, blenderContext); - if (buffers != null) { - mesh.setMaxNumWeights(bonesGroups[0]); - mesh.setBuffer(buffers[0]); - mesh.setBuffer(buffers[1]); - - VertexBuffer bindNormalBuffer = (meshContext.getBindNormalBuffer(materialIndex)); - if(bindNormalBuffer != null) { - mesh.setBuffer(bindNormalBuffer); - } - VertexBuffer bindPoseBuffer = (meshContext.getBindPoseBuffer(materialIndex)); - if(bindPoseBuffer != null) { - mesh.setBuffer(bindPoseBuffer); - } - //change the usage type of vertex and normal buffers from Static to Stream - mesh.getBuffer(Type.Position).setUsage(Usage.Stream); - mesh.getBuffer(Type.Normal).setUsage(Usage.Stream); - } - } catch (BlenderFileException e) { - LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); - this.invalid = true; - return node; - } - } - - // applying animations - AnimControl control = new AnimControl(animData.skeleton); - ArrayList animList = animData.anims; - if (animList != null && animList.size() > 0) { - HashMap anims = new HashMap(animList.size()); - for (int i = 0; i < animList.size(); ++i) { - Animation animation = animList.get(i); - anims.put(animation.getName(), animation); - } - control.setAnimations(anims); - } - node.addControl(control); - node.addControl(new SkeletonControl(animData.skeleton)); - - return node; - } - - /** - * This method reads mesh indexes - * - * @param objectStructure - * structure of the object that has the armature modifier applied - * @param meshStructure - * the structure of the object's mesh - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blend file structure is - * somehow invalid or corrupted - */ - private VertexBuffer[] readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, int materialIndex, - int[] bonesGroups, BlenderContext blenderContext) throws BlenderFileException { - ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); - Structure defBase = (Structure) objectStructure.getFieldValue("defbase"); - Map groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton, blenderContext); - - MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress()); - - return this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexCount(materialIndex), - bonesGroups, meshContext.getVertexReferenceMap(materialIndex), groupToBoneIndexMap, blenderContext); - } - - /** - * This method returns an array of size 2. The first element is a vertex - * buffer holding bone weights for every vertex in the model. The second - * element is a vertex buffer holding bone indices for vertices (the indices - * of bones the vertices are assigned to). - * - * @param meshStructure - * the mesh structure object - * @param vertexListSize - * a number of vertices in the model - * @param bonesGroups - * this is an output parameter, it should be a one-sized array; - * the maximum amount of weights per vertex (up to - * MAXIMUM_WEIGHTS_PER_VERTEX) is stored there - * @param vertexReferenceMap - * this reference map allows to map the original vertices read - * from blender to vertices that are really in the model; one - * vertex may appear several times in the result model - * @param groupToBoneIndexMap - * this object maps the group index (to which a vertices in - * blender belong) to bone index of the model - * @param blenderContext - * the blender context - * @return arrays of vertices weights and their bone indices and (as an - * output parameter) the maximum amount of weights for a vertex - * @throws BlenderFileException - * this exception is thrown when the blend file structure is - * somehow invalid or corrupted - */ - private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map> vertexReferenceMap, Map groupToBoneIndexMap, BlenderContext blenderContext) - throws BlenderFileException { - bonesGroups[0] = 0; - Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices - FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); - ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); - - if (pDvert.isNotNull()) {// assigning weights and bone indices - boolean warnAboutTooManyVertexWeights = false; - List dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per vertex in blender) - int vertexIndex = 0; - //use tree map to sort weights from the lowest to the highest ones - TreeMap weightToIndexMap = new TreeMap(); - - for (Structure dvert : dverts) { - List vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here - if(vertexIndices != null) { - int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex (max. 4 in JME) - Pointer pDW = (Pointer) dvert.getFieldValue("dw"); - if (totweight > 0 && groupToBoneIndexMap != null) { - weightToIndexMap.clear(); - int weightIndex = 0; - List dw = pDW.fetchData(blenderContext.getInputStream()); - for (Structure deformWeight : dw) { - Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue()); - // null here means that we came accross group that has no bone attached to - if (boneIndex != null) { - float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); - if(weightIndex < MAXIMUM_WEIGHTS_PER_VERTEX) { - if (weight == 0.0f) { - boneIndex = Integer.valueOf(0); - } - // we apply the weight to all referenced vertices - for (Integer index : vertexIndices) { - weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight); - indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue()); - } - weightToIndexMap.put(weight, weightIndex); - bonesGroups[0] = Math.max(bonesGroups[0], weightIndex + 1); - } else if(weight > 0) {//if weight is zero the simply ignore it - warnAboutTooManyVertexWeights = true; - Entry lowestWeightAndIndex = weightToIndexMap.firstEntry(); - if(lowestWeightAndIndex != null && lowestWeightAndIndex.getKey() < weight) { - weightsFloatData.put(lowestWeightAndIndex.getValue(), weight); - indicesData.put(lowestWeightAndIndex.getValue(), boneIndex.byteValue()); - weightToIndexMap.remove(lowestWeightAndIndex.getKey()); - weightToIndexMap.put(weight, lowestWeightAndIndex.getValue()); - } - } - } - ++weightIndex; - } - } else { - // 0.0 weight indicates, do not transform this vertex, but keep it in bind pose. - for (Integer index : vertexIndices) { - weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 0.0f); - indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); - } - } - } - ++vertexIndex; - } - - if(warnAboutTooManyVertexWeights) { - LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName()); - } - } else { - // always bind all vertices to 0-indexed bone - // this bone makes the model look normally if vertices have no bone - // assigned and it is used in object animation, so if we come accross object - // animation we can use the 0-indexed bone for this - for (List vertexIndexList : vertexReferenceMap.values()) { - // we apply the weight to all referenced vertices - for (Integer index : vertexIndexList) { - weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f); - indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); - } - } - } - - bonesGroups[0] = Math.max(bonesGroups[0], 1); - - this.endBoneAssigns(vertexListSize, weightsFloatData); - VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight); - verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData); - - VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex); - verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData); - return new VertexBuffer[] { verticesWeights, verticesWeightsIndices }; - } - - /** - * Normalizes weights if needed and finds largest amount of weights used for - * all vertices in the buffer. - * - * @param vertCount - * amount of vertices - * @param weightsFloatData - * weights for vertices - */ - private void endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) { - weightsFloatData.rewind(); - float[] weights = new float[MAXIMUM_WEIGHTS_PER_VERTEX]; - for (int v = 0; v < vertCount; ++v) { - float sum = 0; - for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) { - weights[i] = weightsFloatData.get(); - sum += weights[i]; - } - if (sum != 1f && sum != 0.0f) { - weightsFloatData.position(weightsFloatData.position() - MAXIMUM_WEIGHTS_PER_VERTEX); - // compute new vals based on sum - float sumToB = 1f / sum; - for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) { - weightsFloatData.put(weights[i] * sumToB); - } - } - } - weightsFloatData.rewind(); - } - - @Override - public String getType() { - return Modifier.ARMATURE_MODIFIER_DATA; - } + private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName()); + private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // JME limitation + + private Skeleton skeleton; + private Structure objectStructure; + private Structure meshStructure; + + /** Loaded animation data. */ + private AnimData animData; + /** Old memory address of the mesh that will have the skeleton applied. */ + private Long meshOMA; + + /** + * This constructor reads animation data from the object structore. The + * stored data is the AnimData and additional data is armature's OMA. + * + * @param objectStructure + * the structure of the object + * @param modifierStructure + * the structure of the modifier + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { + Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0); + Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices + + // if pDvert==null then there are not vertex groups and no need to load + // skeleton (untill bone envelopes are supported) + if (this.validate(modifierStructure, blenderContext) && pDvert.isNotNull()) { + Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object"); + if (pArmatureObject.isNotNull()) { + ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); + + Structure armatureObject = pArmatureObject.fetchData(blenderContext.getInputStream()).get(0); + + // load skeleton + Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0); + + Structure pose = ((Pointer) armatureObject.getFieldValue("pose")).fetchData(blenderContext.getInputStream()).get(0); + List chanbase = ((Structure) pose.getFieldValue("chanbase")).evaluateListBase(blenderContext); + + Map bonesPoseChannels = new HashMap(chanbase.size()); + for (Structure poseChannel : chanbase) { + Pointer pBone = (Pointer) poseChannel.getFieldValue("bone"); + bonesPoseChannels.put(pBone.getOldMemoryAddress(), poseChannel); + } + + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + Matrix4f armatureObjectMatrix = objectHelper.getMatrix(armatureObject, "obmat", true); + Matrix4f inverseMeshObjectMatrix = objectHelper.getMatrix(objectStructure, "obmat", true).invertLocal(); + Matrix4f objectToArmatureTransformation = armatureObjectMatrix.multLocal(inverseMeshObjectMatrix); + + List bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(blenderContext); + List bonesList = new ArrayList(); + for (int i = 0; i < bonebase.size(); ++i) { + armatureHelper.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectToArmatureTransformation, bonesPoseChannels, blenderContext); + } + bonesList.add(0, new Bone("")); + Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]); + skeleton = new Skeleton(bones); + blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton); + this.objectStructure = objectStructure; + this.meshStructure = meshStructure; + + // read mesh indexes + this.meshOMA = meshStructure.getOldMemoryAddress(); + + // read animations + ArrayList animations = new ArrayList(); + List actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); + if (actionHeaders != null) {// it may happen that the model has armature with no actions + for (FileBlockHeader header : actionHeaders) { + Structure actionStructure = header.getStructure(blenderContext); + String actionName = actionStructure.getName(); + + BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext); + if (tracks != null && tracks.length > 0) { + // determining the animation time + float maximumTrackLength = 0; + for (BoneTrack track : tracks) { + float length = track.getLength(); + if (length > maximumTrackLength) { + maximumTrackLength = length; + } + } + + Animation boneAnimation = new Animation(actionName, maximumTrackLength); + boneAnimation.setTracks(tracks); + animations.add(boneAnimation); + } + } + } + // fetching action defined in object + Pointer pAction = (Pointer) objectStructure.getFieldValue("action"); + if (pAction.isNotNull()) { + Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0); + String actionName = actionStructure.getName(); + + BoneTrack[] tracks = armatureHelper.getTracks(actionStructure, skeleton, blenderContext); + if (tracks != null && tracks.length > 0) { + // determining the animation time + float maximumTrackLength = 0; + for (BoneTrack track : tracks) { + float length = track.getLength(); + if (length > maximumTrackLength) { + maximumTrackLength = length; + } + } + + Animation boneAnimation = new Animation(actionName, maximumTrackLength); + boneAnimation.setTracks(tracks); + animations.add(boneAnimation); + } + } + + animData = new AnimData(skeleton, animations); + + // store the animation data for each bone + for (Bone bone : bones) { + Long boneOma = armatureHelper.getBoneOMA(bone); + if (boneOma != null) { + blenderContext.setAnimData(boneOma, animData); + } + } + } else { + modifying = false; + } + } + } + + @Override + @SuppressWarnings("unchecked") + public Node apply(Node node, BlenderContext blenderContext) { + if (invalid) { + LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); + }// if invalid, animData will be null + if (animData == null || skeleton == null) { + return node; + } + + // setting weights for bones + List geomList = (List) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE); + MeshContext meshContext = blenderContext.getMeshContext(meshOMA); + int[] bonesGroups = new int[] { 0 }; + for (Geometry geom : geomList) { + int materialIndex = meshContext.getMaterialIndex(geom); + Mesh mesh = geom.getMesh(); + + try { + VertexBuffer[] buffers = this.readVerticesWeightsData(objectStructure, meshStructure, skeleton, materialIndex, bonesGroups, blenderContext); + if (buffers != null) { + mesh.setMaxNumWeights(bonesGroups[0]); + mesh.setBuffer(buffers[0]); + mesh.setBuffer(buffers[1]); + + VertexBuffer bindNormalBuffer = (meshContext.getBindNormalBuffer(materialIndex)); + if (bindNormalBuffer != null) { + mesh.setBuffer(bindNormalBuffer); + } + VertexBuffer bindPoseBuffer = (meshContext.getBindPoseBuffer(materialIndex)); + if (bindPoseBuffer != null) { + mesh.setBuffer(bindPoseBuffer); + } + // change the usage type of vertex and normal buffers from Static to Stream + mesh.getBuffer(Type.Position).setUsage(Usage.Stream); + mesh.getBuffer(Type.Normal).setUsage(Usage.Stream); + } + } catch (BlenderFileException e) { + LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); + this.invalid = true; + return node; + } + } + + // applying animations + AnimControl control = new AnimControl(animData.skeleton); + ArrayList animList = animData.anims; + if (animList != null && animList.size() > 0) { + HashMap anims = new HashMap(animList.size()); + for (int i = 0; i < animList.size(); ++i) { + Animation animation = animList.get(i); + anims.put(animation.getName(), animation); + } + control.setAnimations(anims); + } + node.addControl(control); + node.addControl(new SkeletonControl(animData.skeleton)); + + return node; + } + + /** + * This method reads mesh indexes + * + * @param objectStructure + * structure of the object that has the armature modifier applied + * @param meshStructure + * the structure of the object's mesh + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blend file structure is + * somehow invalid or corrupted + */ + private VertexBuffer[] readVerticesWeightsData(Structure objectStructure, Structure meshStructure, Skeleton skeleton, int materialIndex, int[] bonesGroups, BlenderContext blenderContext) throws BlenderFileException { + ArmatureHelper armatureHelper = blenderContext.getHelper(ArmatureHelper.class); + Structure defBase = (Structure) objectStructure.getFieldValue("defbase"); + Map groupToBoneIndexMap = armatureHelper.getGroupToBoneIndexMap(defBase, skeleton, blenderContext); + + MeshContext meshContext = blenderContext.getMeshContext(meshStructure.getOldMemoryAddress()); + + return this.getBoneWeightAndIndexBuffer(meshStructure, meshContext.getVertexCount(materialIndex), bonesGroups, meshContext.getVertexReferenceMap(materialIndex), groupToBoneIndexMap, blenderContext); + } + + /** + * This method returns an array of size 2. The first element is a vertex + * buffer holding bone weights for every vertex in the model. The second + * element is a vertex buffer holding bone indices for vertices (the indices + * of bones the vertices are assigned to). + * + * @param meshStructure + * the mesh structure object + * @param vertexListSize + * a number of vertices in the model + * @param bonesGroups + * this is an output parameter, it should be a one-sized array; + * the maximum amount of weights per vertex (up to + * MAXIMUM_WEIGHTS_PER_VERTEX) is stored there + * @param vertexReferenceMap + * this reference map allows to map the original vertices read + * from blender to vertices that are really in the model; one + * vertex may appear several times in the result model + * @param groupToBoneIndexMap + * this object maps the group index (to which a vertices in + * blender belong) to bone index of the model + * @param blenderContext + * the blender context + * @return arrays of vertices weights and their bone indices and (as an + * output parameter) the maximum amount of weights for a vertex + * @throws BlenderFileException + * this exception is thrown when the blend file structure is + * somehow invalid or corrupted + */ + private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map> vertexReferenceMap, Map groupToBoneIndexMap, BlenderContext blenderContext) throws BlenderFileException { + bonesGroups[0] = 0; + Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices + FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); + ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); + + if (pDvert.isNotNull()) {// assigning weights and bone indices + boolean warnAboutTooManyVertexWeights = false; + List dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per vertex in blender) + int vertexIndex = 0; + // use tree map to sort weights from the lowest to the highest ones + TreeMap weightToIndexMap = new TreeMap(); + + for (Structure dvert : dverts) { + List vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here + if (vertexIndices != null) { + int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex (max. 4 in JME) + Pointer pDW = (Pointer) dvert.getFieldValue("dw"); + if (totweight > 0 && groupToBoneIndexMap != null) { + weightToIndexMap.clear(); + int weightIndex = 0; + List dw = pDW.fetchData(blenderContext.getInputStream()); + for (Structure deformWeight : dw) { + Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue()); + // null here means that we came accross group that has no bone attached to + if (boneIndex != null) { + float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); + if (weightIndex < MAXIMUM_WEIGHTS_PER_VERTEX) { + if (weight == 0.0f) { + boneIndex = Integer.valueOf(0); + } + // we apply the weight to all referenced vertices + for (Integer index : vertexIndices) { + weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight); + indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue()); + } + weightToIndexMap.put(weight, weightIndex); + bonesGroups[0] = Math.max(bonesGroups[0], weightIndex + 1); + } else if (weight > 0) {// if weight is zero the simply ignore it + warnAboutTooManyVertexWeights = true; + Entry lowestWeightAndIndex = weightToIndexMap.firstEntry(); + if (lowestWeightAndIndex != null && lowestWeightAndIndex.getKey() < weight) { + weightsFloatData.put(lowestWeightAndIndex.getValue(), weight); + indicesData.put(lowestWeightAndIndex.getValue(), boneIndex.byteValue()); + weightToIndexMap.remove(lowestWeightAndIndex.getKey()); + weightToIndexMap.put(weight, lowestWeightAndIndex.getValue()); + } + } + } + ++weightIndex; + } + } else { + // 0.0 weight indicates, do not transform this vertex, but keep it in bind pose. + for (Integer index : vertexIndices) { + weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 0.0f); + indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); + } + } + } + ++vertexIndex; + } + + if (warnAboutTooManyVertexWeights) { + LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName()); + } + } else { + // always bind all vertices to 0-indexed bone + // this bone makes the model look normally if vertices have no bone + // assigned and it is used in object animation, so if we come accross object + // animation we can use the 0-indexed bone for this + for (List vertexIndexList : vertexReferenceMap.values()) { + // we apply the weight to all referenced vertices + for (Integer index : vertexIndexList) { + weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f); + indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); + } + } + } + + bonesGroups[0] = Math.max(bonesGroups[0], 1); + + this.endBoneAssigns(vertexListSize, weightsFloatData); + VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight); + verticesWeights.setupData(Usage.CpuOnly, bonesGroups[0], Format.Float, weightsFloatData); + + VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex); + verticesWeightsIndices.setupData(Usage.CpuOnly, bonesGroups[0], Format.UnsignedByte, indicesData); + return new VertexBuffer[] { verticesWeights, verticesWeightsIndices }; + } + + /** + * Normalizes weights if needed and finds largest amount of weights used for + * all vertices in the buffer. + * + * @param vertCount + * amount of vertices + * @param weightsFloatData + * weights for vertices + */ + private void endBoneAssigns(int vertCount, FloatBuffer weightsFloatData) { + weightsFloatData.rewind(); + float[] weights = new float[MAXIMUM_WEIGHTS_PER_VERTEX]; + for (int v = 0; v < vertCount; ++v) { + float sum = 0; + for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) { + weights[i] = weightsFloatData.get(); + sum += weights[i]; + } + if (sum != 1f && sum != 0.0f) { + weightsFloatData.position(weightsFloatData.position() - MAXIMUM_WEIGHTS_PER_VERTEX); + // compute new vals based on sum + float sumToB = 1f / sum; + for (int i = 0; i < MAXIMUM_WEIGHTS_PER_VERTEX; ++i) { + weightsFloatData.put(weights[i] * sumToB); + } + } + } + weightsFloatData.rewind(); + } + + @Override + public String getType() { + return Modifier.ARMATURE_MODIFIER_DATA; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java index 4997fdc25..78e5c6def 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java @@ -29,116 +29,116 @@ import java.util.logging.Logger; * * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ArrayModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName()); - - /** Parameters of the modifier. */ - private Map modifierData = new HashMap(); - - /** - * This constructor reads array data from the modifier structure. The - * stored data is a map of parameters for array modifier. No additional data - * is loaded. - * - * @param objectStructure - * the structure of the object - * @param modifierStructure - * the structure of the modifier - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - @SuppressWarnings("unchecked") - public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { - if(this.validate(modifierStructure, blenderContext)) { - Number fittype = (Number) modifierStructure.getFieldValue("fit_type"); - modifierData.put("fittype", fittype); - switch (fittype.intValue()) { - case 0:// FIXED COUNT - modifierData.put("count", modifierStructure.getFieldValue("count")); - break; - case 1:// FIXED LENGTH - modifierData.put("length", modifierStructure.getFieldValue("length")); - break; - case 2:// FITCURVE - Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob"); - float length = 0; - if (pCurveOb.isNotNull()) { - Structure curveStructure = pCurveOb.fetchData(blenderContext.getInputStream()).get(0); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Node curveObject = (Node) objectHelper.toObject(curveStructure, blenderContext); - Set referencesToCurveLengths = new HashSet(curveObject.getChildren().size()); - for (Spatial spatial : curveObject.getChildren()) { - if (spatial instanceof Geometry) { - Mesh mesh = ((Geometry) spatial).getMesh(); - if (mesh instanceof Curve) { - length += ((Curve) mesh).getLength(); - } else { - //if bevel object has several parts then each mesh will have the same reference - //to length value (and we should use only one) - Number curveLength = spatial.getUserData("curveLength"); - if (curveLength != null && !referencesToCurveLengths.contains(curveLength)) { - length += curveLength.floatValue(); - referencesToCurveLengths.add(curveLength); - } - } - } - } - } - modifierData.put("length", Float.valueOf(length)); - modifierData.put("fittype", Integer.valueOf(1));// treat it like FIXED LENGTH - break; - default: - assert false : "Unknown array modifier fit type: " + fittype; - } - - // offset parameters - int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue(); - if ((offsettype & 0x01) != 0) {// Constant offset - DynamicArray offsetArray = (DynamicArray) modifierStructure.getFieldValue("offset"); - float[] offset = new float[]{offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue()}; - modifierData.put("offset", offset); - } - if ((offsettype & 0x02) != 0) {// Relative offset - DynamicArray scaleArray = (DynamicArray) modifierStructure.getFieldValue("scale"); - float[] scale = new float[]{scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()}; - modifierData.put("scale", scale); - } - if ((offsettype & 0x04) != 0) {// Object offset - Pointer pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob"); - if (pOffsetObject.isNotNull()) { - modifierData.put("offsetob", pOffsetObject); - } - } - - // start cap and end cap - Pointer pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap"); - if (pStartCap.isNotNull()) { - modifierData.put("startcap", pStartCap); - } - Pointer pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap"); - if (pEndCap.isNotNull()) { - modifierData.put("endcap", pEndCap); - } - } - } - - @Override - public Node apply(Node node, BlenderContext blenderContext) { - if(invalid) { - LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName()); - return node; - } +/* package */class ArrayModifier extends Modifier { + private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName()); + + /** Parameters of the modifier. */ + private Map modifierData = new HashMap(); + + /** + * This constructor reads array data from the modifier structure. The + * stored data is a map of parameters for array modifier. No additional data + * is loaded. + * + * @param objectStructure + * the structure of the object + * @param modifierStructure + * the structure of the modifier + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + @SuppressWarnings("unchecked") + public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { + if (this.validate(modifierStructure, blenderContext)) { + Number fittype = (Number) modifierStructure.getFieldValue("fit_type"); + modifierData.put("fittype", fittype); + switch (fittype.intValue()) { + case 0:// FIXED COUNT + modifierData.put("count", modifierStructure.getFieldValue("count")); + break; + case 1:// FIXED LENGTH + modifierData.put("length", modifierStructure.getFieldValue("length")); + break; + case 2:// FITCURVE + Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob"); + float length = 0; + if (pCurveOb.isNotNull()) { + Structure curveStructure = pCurveOb.fetchData(blenderContext.getInputStream()).get(0); + ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); + Node curveObject = (Node) objectHelper.toObject(curveStructure, blenderContext); + Set referencesToCurveLengths = new HashSet(curveObject.getChildren().size()); + for (Spatial spatial : curveObject.getChildren()) { + if (spatial instanceof Geometry) { + Mesh mesh = ((Geometry) spatial).getMesh(); + if (mesh instanceof Curve) { + length += ((Curve) mesh).getLength(); + } else { + // if bevel object has several parts then each mesh will have the same reference + // to length value (and we should use only one) + Number curveLength = spatial.getUserData("curveLength"); + if (curveLength != null && !referencesToCurveLengths.contains(curveLength)) { + length += curveLength.floatValue(); + referencesToCurveLengths.add(curveLength); + } + } + } + } + } + modifierData.put("length", Float.valueOf(length)); + modifierData.put("fittype", Integer.valueOf(1));// treat it like FIXED LENGTH + break; + default: + assert false : "Unknown array modifier fit type: " + fittype; + } + + // offset parameters + int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue(); + if ((offsettype & 0x01) != 0) {// Constant offset + DynamicArray offsetArray = (DynamicArray) modifierStructure.getFieldValue("offset"); + float[] offset = new float[] { offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue() }; + modifierData.put("offset", offset); + } + if ((offsettype & 0x02) != 0) {// Relative offset + DynamicArray scaleArray = (DynamicArray) modifierStructure.getFieldValue("scale"); + float[] scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() }; + modifierData.put("scale", scale); + } + if ((offsettype & 0x04) != 0) {// Object offset + Pointer pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob"); + if (pOffsetObject.isNotNull()) { + modifierData.put("offsetob", pOffsetObject); + } + } + + // start cap and end cap + Pointer pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap"); + if (pStartCap.isNotNull()) { + modifierData.put("startcap", pStartCap); + } + Pointer pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap"); + if (pEndCap.isNotNull()) { + modifierData.put("endcap", pEndCap); + } + } + } + + @Override + public Node apply(Node node, BlenderContext blenderContext) { + if (invalid) { + LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName()); + return node; + } int fittype = ((Number) modifierData.get("fittype")).intValue(); float[] offset = (float[]) modifierData.get("offset"); if (offset == null) {// the node will be repeated several times in the same place - offset = new float[]{0.0f, 0.0f, 0.0f}; + offset = new float[] { 0.0f, 0.0f, 0.0f }; } float[] scale = (float[]) modifierData.get("scale"); if (scale == null) {// the node will be repeated several times in the same place - scale = new float[]{0.0f, 0.0f, 0.0f}; + scale = new float[] { 0.0f, 0.0f, 0.0f }; } else { // getting bounding box node.updateModelBound(); @@ -158,7 +158,7 @@ import java.util.logging.Logger; } // adding object's offset - float[] objectOffset = new float[]{0.0f, 0.0f, 0.0f}; + float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f }; Pointer pOffsetObject = (Pointer) modifierData.get("offsetob"); if (pOffsetObject != null) { FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress()); @@ -175,8 +175,8 @@ import java.util.logging.Logger; } // getting start and end caps - Node[] caps = new Node[]{null, null}; - Pointer[] pCaps = new Pointer[]{(Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap")}; + Node[] caps = new Node[] { null, null }; + Pointer[] pCaps = new Pointer[] { (Pointer) modifierData.get("startcap"), (Pointer) modifierData.get("endcap") }; for (int i = 0; i < pCaps.length; ++i) { if (pCaps[i] != null) { caps[i] = (Node) blenderContext.getLoadedFeature(pCaps[i].getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); @@ -238,10 +238,10 @@ import java.util.logging.Logger; } } return node; - } - - @Override - public String getType() { - return ARRAY_MODIFIER_DATA; - } + } + + @Override + public String getType() { + return ARRAY_MODIFIER_DATA; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java index 0d82cce51..fbce73f25 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java @@ -30,58 +30,54 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; * * @author Marcin Roguski (Kaelthas) */ -/*package*/ class MirrorModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); - - /** Parameters of the modifier. */ - private Map modifierData = new HashMap(); - - /** - * This constructor reads mirror data from the modifier structure. The - * stored data is a map of parameters for mirror modifier. No additional data - * is loaded. - * When the modifier is applied it is necessary to get the newly created node. - * - * @param objectStructure - * the structure of the object - * @param modifierStructure - * the structure of the modifier - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public MirrorModifier(Structure modifierStructure, BlenderContext blenderContext) { - if(this.validate(modifierStructure, blenderContext)) { - modifierData.put("flag", modifierStructure.getFieldValue("flag")); - modifierData.put("tolerance", modifierStructure.getFieldValue("tolerance")); - Pointer pMirrorOb = (Pointer) modifierStructure.getFieldValue("mirror_ob"); - if (pMirrorOb.isNotNull()) { - modifierData.put("mirrorob", pMirrorOb); - } - } - } - - @Override - public Node apply(Node node, BlenderContext blenderContext) { - if(invalid) { - LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName()); - return node; - } - +/* package */class MirrorModifier extends Modifier { + private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); + + /** Parameters of the modifier. */ + private Map modifierData = new HashMap(); + + /** + * This constructor reads mirror data from the modifier structure. The + * stored data is a map of parameters for mirror modifier. No additional data + * is loaded. + * When the modifier is applied it is necessary to get the newly created node. + * + * @param objectStructure + * the structure of the object + * @param modifierStructure + * the structure of the modifier + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public MirrorModifier(Structure modifierStructure, BlenderContext blenderContext) { + if (this.validate(modifierStructure, blenderContext)) { + modifierData.put("flag", modifierStructure.getFieldValue("flag")); + modifierData.put("tolerance", modifierStructure.getFieldValue("tolerance")); + Pointer pMirrorOb = (Pointer) modifierStructure.getFieldValue("mirror_ob"); + if (pMirrorOb.isNotNull()) { + modifierData.put("mirrorob", pMirrorOb); + } + } + } + + @Override + public Node apply(Node node, BlenderContext blenderContext) { + if (invalid) { + LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName()); + return node; + } + int flag = ((Number) modifierData.get("flag")).intValue(); - float[] mirrorFactor = new float[]{ - (flag & 0x08) != 0 ? -1.0f : 1.0f, - (flag & 0x10) != 0 ? -1.0f : 1.0f, - (flag & 0x20) != 0 ? -1.0f : 1.0f - }; - if(blenderContext.getBlenderKey().isFixUpAxis()) { - float temp = mirrorFactor[1]; - mirrorFactor[1] = mirrorFactor[2]; - mirrorFactor[2] = temp; + float[] mirrorFactor = new float[] { (flag & 0x08) != 0 ? -1.0f : 1.0f, (flag & 0x10) != 0 ? -1.0f : 1.0f, (flag & 0x20) != 0 ? -1.0f : 1.0f }; + if (blenderContext.getBlenderKey().isFixUpAxis()) { + float temp = mirrorFactor[1]; + mirrorFactor[1] = mirrorFactor[2]; + mirrorFactor[2] = temp; } - float[] center = new float[]{0.0f, 0.0f, 0.0f}; + float[] center = new float[] { 0.0f, 0.0f, 0.0f }; Pointer pObject = (Pointer) modifierData.get("mirrorob"); if (pObject != null) { Structure objectStructure; @@ -102,7 +98,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; float tolerance = ((Number) modifierData.get("tolerance")).floatValue(); boolean mirrorU = (flag & 0x01) != 0; boolean mirrorV = (flag & 0x02) != 0; -// boolean mirrorVGroup = (flag & 0x20) != 0; + // boolean mirrorVGroup = (flag & 0x20) != 0; Set modifiedIndexes = new HashSet(); List geometriesToAdd = new ArrayList(); @@ -122,50 +118,50 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; FloatBuffer cloneNormals = clone.getFloatBuffer(Type.Normal); FloatBuffer cloneBindPoseNormals = clone.getFloatBuffer(Type.BindPoseNormal); Buffer cloneIndexes = clone.getBuffer(Type.Index).getData(); - - for (int i = 0; i < cloneIndexes.limit(); ++i) { - int index = cloneIndexes instanceof ShortBuffer ? ((ShortBuffer)cloneIndexes).get(i) : ((IntBuffer)cloneIndexes).get(i); - if(!modifiedIndexes.contains((int)index)) { - modifiedIndexes.add((int)index); - int valueIndex = index * 3 + mirrorIndex; - - float value = clonePosition.get(valueIndex); + + for (int i = 0; i < cloneIndexes.limit(); ++i) { + int index = cloneIndexes instanceof ShortBuffer ? ((ShortBuffer) cloneIndexes).get(i) : ((IntBuffer) cloneIndexes).get(i); + if (!modifiedIndexes.contains((int) index)) { + modifiedIndexes.add((int) index); + int valueIndex = index * 3 + mirrorIndex; + + float value = clonePosition.get(valueIndex); float d = center[mirrorIndex] - value; if (Math.abs(d) <= tolerance) { clonePosition.put(valueIndex, center[mirrorIndex]); - if(cloneBindPosePosition != null) { - cloneBindPosePosition.put(valueIndex, center[mirrorIndex]); + if (cloneBindPosePosition != null) { + cloneBindPosePosition.put(valueIndex, center[mirrorIndex]); } position.put(valueIndex, center[mirrorIndex]); - if(bindPosePosition != null) { - bindPosePosition.put(valueIndex, center[mirrorIndex]); + if (bindPosePosition != null) { + bindPosePosition.put(valueIndex, center[mirrorIndex]); } } else { clonePosition.put(valueIndex, value + 2.0f * d); - if(cloneBindPosePosition != null) { - cloneBindPosePosition.put(valueIndex, value + 2.0f * d); + if (cloneBindPosePosition != null) { + cloneBindPosePosition.put(valueIndex, value + 2.0f * d); } } cloneNormals.put(valueIndex, -cloneNormals.get(valueIndex)); - if(cloneBindPoseNormals != null) { - cloneBindPoseNormals.put(valueIndex, -cloneNormals.get(valueIndex)); + if (cloneBindPoseNormals != null) { + cloneBindPoseNormals.put(valueIndex, -cloneNormals.get(valueIndex)); } - } + } } modifiedIndexes.clear(); - - //flipping index order - for (int i = 0; i < cloneIndexes.limit(); i += 3) { - if(cloneIndexes instanceof ShortBuffer) { - short index = ((ShortBuffer)cloneIndexes).get(i + 2); - ((ShortBuffer)cloneIndexes).put(i + 2, ((ShortBuffer)cloneIndexes).get(i + 1)); - ((ShortBuffer)cloneIndexes).put(i + 1, index); - } else { - int index = ((IntBuffer)cloneIndexes).get(i + 2); - ((IntBuffer)cloneIndexes).put(i + 2, ((IntBuffer)cloneIndexes).get(i + 1)); - ((IntBuffer)cloneIndexes).put(i + 1, index); - } + + // flipping index order + for (int i = 0; i < cloneIndexes.limit(); i += 3) { + if (cloneIndexes instanceof ShortBuffer) { + short index = ((ShortBuffer) cloneIndexes).get(i + 2); + ((ShortBuffer) cloneIndexes).put(i + 2, ((ShortBuffer) cloneIndexes).get(i + 1)); + ((ShortBuffer) cloneIndexes).put(i + 1, index); + } else { + int index = ((IntBuffer) cloneIndexes).get(i + 2); + ((IntBuffer) cloneIndexes).put(i + 2, ((IntBuffer) cloneIndexes).get(i + 1)); + ((IntBuffer) cloneIndexes).put(i + 1, index); + } } if (mirrorU && clone.getBuffer(Type.TexCoord) != null) { @@ -195,10 +191,10 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; } } return node; - } - - @Override - public String getType() { - return Modifier.MIRROR_MODIFIER_DATA; - } + } + + @Override + public String getType() { + return Modifier.MIRROR_MODIFIER_DATA; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/Modifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/Modifier.java index 6138dae5f..862c0256b 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/Modifier.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/Modifier.java @@ -14,65 +14,65 @@ import com.jme3.scene.plugins.blender.file.Structure; * @author Marcin Roguski (Kaelthas) */ public abstract class Modifier { - public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData"; - public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData"; - public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData"; - public static final String MIRROR_MODIFIER_DATA = "MirrorModifierData"; - public static final String SUBSURF_MODIFIER_DATA = "SubsurfModifierData"; - public static final String OBJECT_ANIMATION_MODIFIER_DATA = "ObjectAnimationModifierData"; + public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData"; + public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData"; + public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData"; + public static final String MIRROR_MODIFIER_DATA = "MirrorModifierData"; + public static final String SUBSURF_MODIFIER_DATA = "SubsurfModifierData"; + public static final String OBJECT_ANIMATION_MODIFIER_DATA = "ObjectAnimationModifierData"; - /** This variable indicates if the modifier is invalid (true) or not (false). */ - protected boolean invalid; - /** - * A variable that tells if the modifier causes modification. Some modifiers like ArmatureModifier might have no - * Armature object attached and thus not really modifying the feature. In such cases it is good to know if it is - * sense to add the modifier to the list of object's modifiers. - */ - protected boolean modifying = true; - - /** - * This method applies the modifier to the given node. - * - * @param node - * the node that will have modifier applied - * @param blenderContext - * the blender context - * @return the node with applied modifier - */ - public abstract Node apply(Node node, BlenderContext blenderContext); + /** This variable indicates if the modifier is invalid (true) or not (false). */ + protected boolean invalid; + /** + * A variable that tells if the modifier causes modification. Some modifiers like ArmatureModifier might have no + * Armature object attached and thus not really modifying the feature. In such cases it is good to know if it is + * sense to add the modifier to the list of object's modifiers. + */ + protected boolean modifying = true; - /** - * This method returns blender's type of modifier. - * - * @return blender's type of modifier - */ - public abstract String getType(); - - /** - * Determines if the modifier can be applied multiple times over one mesh. - * At this moment only armature and object animation modifiers cannot be - * applied multiple times. - * - * @param modifierType - * the type name of the modifier - * @return true if the modifier can be applied many times and - * false otherwise - */ - public static boolean canBeAppliedMultipleTimes(String modifierType) { - return !(ARMATURE_MODIFIER_DATA.equals(modifierType) || OBJECT_ANIMATION_MODIFIER_DATA.equals(modifierType)); - } - - protected boolean validate(Structure modifierStructure, BlenderContext blenderContext) { - Structure modifierData = (Structure)modifierStructure.getFieldValue("modifier"); - Pointer pError = (Pointer) modifierData.getFieldValue("error"); - invalid = pError.isNotNull(); - return !invalid; - } - - /** - * @return true if the modifier causes feature's modification or false if not - */ - public boolean isModifying() { - return modifying; - } + /** + * This method applies the modifier to the given node. + * + * @param node + * the node that will have modifier applied + * @param blenderContext + * the blender context + * @return the node with applied modifier + */ + public abstract Node apply(Node node, BlenderContext blenderContext); + + /** + * This method returns blender's type of modifier. + * + * @return blender's type of modifier + */ + public abstract String getType(); + + /** + * Determines if the modifier can be applied multiple times over one mesh. + * At this moment only armature and object animation modifiers cannot be + * applied multiple times. + * + * @param modifierType + * the type name of the modifier + * @return true if the modifier can be applied many times and + * false otherwise + */ + public static boolean canBeAppliedMultipleTimes(String modifierType) { + return !(ARMATURE_MODIFIER_DATA.equals(modifierType) || OBJECT_ANIMATION_MODIFIER_DATA.equals(modifierType)); + } + + protected boolean validate(Structure modifierStructure, BlenderContext blenderContext) { + Structure modifierData = (Structure) modifierStructure.getFieldValue("modifier"); + Pointer pError = (Pointer) modifierData.getFieldValue("error"); + invalid = pError.isNotNull(); + return !invalid; + } + + /** + * @return true if the modifier causes feature's modification or false if not + */ + public boolean isModifying() { + return modifying; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java index 4780efcd0..815ce6a25 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java @@ -53,143 +53,143 @@ import java.util.logging.Logger; */ public class ModifierHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(ModifierHelper.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ModifierHelper.class.getName()); - /** - * This constructor parses the given blender version and stores the result. - * Some functionalities may differ in different blender versions. - * - * @param blenderVersion - * the version read from the blend file - * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not - */ - public ModifierHelper(String blenderVersion, boolean fixUpAxis) { - super(blenderVersion, fixUpAxis); - } + /** + * This constructor parses the given blender version and stores the result. + * Some functionalities may differ in different blender versions. + * + * @param blenderVersion + * the version read from the blend file + * @param fixUpAxis + * a variable that indicates if the Y asxis is the UP axis or not + */ + public ModifierHelper(String blenderVersion, boolean fixUpAxis) { + super(blenderVersion, fixUpAxis); + } - /** - * This method reads the given object's modifiers. - * - * @param objectStructure - * the object structure - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public Collection readModifiers(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - Set alreadyReadModifiers = new HashSet(); - Collection result = new ArrayList(); - Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers"); - List modifiers = modifiersListBase.evaluateListBase(blenderContext); - for (Structure modifierStructure : modifiers) { - String modifierType = modifierStructure.getType(); - if(!Modifier.canBeAppliedMultipleTimes(modifierType) && alreadyReadModifiers.contains(modifierType)) { - LOGGER.log(Level.WARNING, "Modifier {0} can only be applied once to object: {1}", new Object[] { modifierType, objectStructure.getName() }); - } else { - Modifier modifier = null; - if (Modifier.ARRAY_MODIFIER_DATA.equals(modifierStructure.getType())) { - modifier = new ArrayModifier(modifierStructure, blenderContext); - } else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifierStructure.getType())) { - modifier = new MirrorModifier(modifierStructure, blenderContext); - } else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifierStructure.getType())) { - modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext); - } else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) { - modifier = new ParticlesModifier(modifierStructure, blenderContext); - } + /** + * This method reads the given object's modifiers. + * + * @param objectStructure + * the object structure + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public Collection readModifiers(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { + Set alreadyReadModifiers = new HashSet(); + Collection result = new ArrayList(); + Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers"); + List modifiers = modifiersListBase.evaluateListBase(blenderContext); + for (Structure modifierStructure : modifiers) { + String modifierType = modifierStructure.getType(); + if (!Modifier.canBeAppliedMultipleTimes(modifierType) && alreadyReadModifiers.contains(modifierType)) { + LOGGER.log(Level.WARNING, "Modifier {0} can only be applied once to object: {1}", new Object[] { modifierType, objectStructure.getName() }); + } else { + Modifier modifier = null; + if (Modifier.ARRAY_MODIFIER_DATA.equals(modifierStructure.getType())) { + modifier = new ArrayModifier(modifierStructure, blenderContext); + } else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifierStructure.getType())) { + modifier = new MirrorModifier(modifierStructure, blenderContext); + } else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifierStructure.getType())) { + modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext); + } else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) { + modifier = new ParticlesModifier(modifierStructure, blenderContext); + } - if (modifier != null) { - if(modifier.isModifying()) { - result.add(modifier); - blenderContext.addModifier(objectStructure.getOldMemoryAddress(), modifier); - alreadyReadModifiers.add(modifierType); - } else { - LOGGER.log(Level.WARNING, "The modifier {0} will cause no changes in the model. It will be ignored!", modifierStructure.getName()); - } - } else { - LOGGER.log(Level.WARNING, "Unsupported modifier type: {0}", modifierStructure.getType()); - } - } - } + if (modifier != null) { + if (modifier.isModifying()) { + result.add(modifier); + blenderContext.addModifier(objectStructure.getOldMemoryAddress(), modifier); + alreadyReadModifiers.add(modifierType); + } else { + LOGGER.log(Level.WARNING, "The modifier {0} will cause no changes in the model. It will be ignored!", modifierStructure.getName()); + } + } else { + LOGGER.log(Level.WARNING, "Unsupported modifier type: {0}", modifierStructure.getType()); + } + } + } - // at the end read object's animation modifier (object animation is - // either described by action or by ipo of the object) - Modifier modifier; - if (blenderVersion <= 249) { - modifier = this.readAnimationModifier249(objectStructure, blenderContext); - } else { - modifier = this.readAnimationModifier250(objectStructure, blenderContext); - } - if (modifier != null) { - result.add(modifier); - } - return result; - } + // at the end read object's animation modifier (object animation is + // either described by action or by ipo of the object) + Modifier modifier; + if (blenderVersion <= 249) { + modifier = this.readAnimationModifier249(objectStructure, blenderContext); + } else { + modifier = this.readAnimationModifier250(objectStructure, blenderContext); + } + if (modifier != null) { + result.add(modifier); + } + return result; + } - @Override - public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - return true; - } + @Override + public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { + return true; + } - /** - * This method reads the object's animation modifier for blender version - * 2.49 and lower. - * - * @param objectStructure - * the object's structure - * @param blenderContext - * the blender context - * @return loaded modifier - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - private Modifier readAnimationModifier249(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - Modifier result = null; - Pointer pIpo = (Pointer) objectStructure.getFieldValue("ipo"); - if (pIpo.isNotNull()) { - IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); - Structure ipoStructure = pIpo.fetchData(blenderContext.getInputStream()).get(0); - Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext); - if(ipo != null) { - result = new ObjectAnimationModifier(ipo, objectStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext); - blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result); - } - } - return result; - } + /** + * This method reads the object's animation modifier for blender version + * 2.49 and lower. + * + * @param objectStructure + * the object's structure + * @param blenderContext + * the blender context + * @return loaded modifier + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + private Modifier readAnimationModifier249(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { + Modifier result = null; + Pointer pIpo = (Pointer) objectStructure.getFieldValue("ipo"); + if (pIpo.isNotNull()) { + IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); + Structure ipoStructure = pIpo.fetchData(blenderContext.getInputStream()).get(0); + Ipo ipo = ipoHelper.fromIpoStructure(ipoStructure, blenderContext); + if (ipo != null) { + result = new ObjectAnimationModifier(ipo, objectStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext); + blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result); + } + } + return result; + } - /** - * This method reads the object's animation modifier for blender version - * 2.50 and higher. - * - * @param objectStructure - * the object's structure - * @param blenderContext - * the blender context - * @return loaded modifier - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - private Modifier readAnimationModifier250(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - Modifier result = null; - Pointer pAnimData = (Pointer) objectStructure.getFieldValue("adt"); - if (pAnimData.isNotNull()) { - Structure animData = pAnimData.fetchData(blenderContext.getInputStream()).get(0); - Pointer pAction = (Pointer) animData.getFieldValue("action"); - if (pAction.isNotNull()) { - Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0); - IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); - Ipo ipo = ipoHelper.fromAction(actionStructure, blenderContext); - if(ipo != null) {//ipo can be null if it has no curves applied, ommit such modifier then - result = new ObjectAnimationModifier(ipo, actionStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext); - blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result); - } - } - } - return result; - } + /** + * This method reads the object's animation modifier for blender version + * 2.50 and higher. + * + * @param objectStructure + * the object's structure + * @param blenderContext + * the blender context + * @return loaded modifier + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + private Modifier readAnimationModifier250(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { + Modifier result = null; + Pointer pAnimData = (Pointer) objectStructure.getFieldValue("adt"); + if (pAnimData.isNotNull()) { + Structure animData = pAnimData.fetchData(blenderContext.getInputStream()).get(0); + Pointer pAction = (Pointer) animData.getFieldValue("action"); + if (pAction.isNotNull()) { + Structure actionStructure = pAction.fetchData(blenderContext.getInputStream()).get(0); + IpoHelper ipoHelper = blenderContext.getHelper(IpoHelper.class); + Ipo ipo = ipoHelper.fromAction(actionStructure, blenderContext); + if (ipo != null) {// ipo can be null if it has no curves applied, ommit such modifier then + result = new ObjectAnimationModifier(ipo, actionStructure.getName(), objectStructure.getOldMemoryAddress(), blenderContext); + blenderContext.addModifier(objectStructure.getOldMemoryAddress(), result); + } + } + } + return result; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java index 3da91c8a9..0e0030d19 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ObjectAnimationModifier.java @@ -22,73 +22,73 @@ import com.jme3.scene.plugins.ogre.AnimData; * @author Marcin Roguski (Kaelthas) */ /* package */class ObjectAnimationModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(ObjectAnimationModifier.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ObjectAnimationModifier.class.getName()); - /** Loaded animation data. */ - private AnimData animData; + /** Loaded animation data. */ + private AnimData animData; - /** - * This constructor reads animation of the object itself (without bones) and - * stores it as an ArmatureModifierData modifier. The animation is returned - * as a modifier. It should be later applied regardless other modifiers. The - * reason for this is that object may not have modifiers added but it's - * animation should be working. The stored modifier is an anim data and - * additional data is given object's OMA. - * - * @param ipo - * the object's interpolation curves - * @param objectAnimationName - * the name of object's animation - * @param objectOMA - * the OMA of the object - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public ObjectAnimationModifier(Ipo ipo, String objectAnimationName, Long objectOMA, BlenderContext blenderContext) throws BlenderFileException { - int fps = blenderContext.getBlenderKey().getFps(); - - Spatial object = (Spatial) blenderContext.getLoadedFeature(objectOMA, LoadedFeatureDataType.LOADED_FEATURE); - // calculating track - SpatialTrack track = (SpatialTrack) ipo.calculateTrack(-1, object.getLocalRotation(), 0, ipo.getLastFrame(), fps, true); + /** + * This constructor reads animation of the object itself (without bones) and + * stores it as an ArmatureModifierData modifier. The animation is returned + * as a modifier. It should be later applied regardless other modifiers. The + * reason for this is that object may not have modifiers added but it's + * animation should be working. The stored modifier is an anim data and + * additional data is given object's OMA. + * + * @param ipo + * the object's interpolation curves + * @param objectAnimationName + * the name of object's animation + * @param objectOMA + * the OMA of the object + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this exception is thrown when the blender file is somehow + * corrupted + */ + public ObjectAnimationModifier(Ipo ipo, String objectAnimationName, Long objectOMA, BlenderContext blenderContext) throws BlenderFileException { + int fps = blenderContext.getBlenderKey().getFps(); - Animation animation = new Animation(objectAnimationName, ipo.getLastFrame() / (float)fps); - animation.setTracks(new SpatialTrack[] { track }); - ArrayList animations = new ArrayList(1); - animations.add(animation); + Spatial object = (Spatial) blenderContext.getLoadedFeature(objectOMA, LoadedFeatureDataType.LOADED_FEATURE); + // calculating track + SpatialTrack track = (SpatialTrack) ipo.calculateTrack(-1, object.getLocalRotation(), 0, ipo.getLastFrame(), fps, true); - animData = new AnimData(null, animations); - blenderContext.setAnimData(objectOMA, animData); - } + Animation animation = new Animation(objectAnimationName, ipo.getLastFrame() / (float) fps); + animation.setTracks(new SpatialTrack[] { track }); + ArrayList animations = new ArrayList(1); + animations.add(animation); - @Override - public Node apply(Node node, BlenderContext blenderContext) { - if (invalid) { - LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); - }// if invalid, animData will be null - if (animData != null) { - // INFO: constraints for this modifier are applied in the - // ObjectHelper when the whole object is loaded - ArrayList animList = animData.anims; - if (animList != null && animList.size() > 0) { - HashMap anims = new HashMap(); - for (int i = 0; i < animList.size(); ++i) { - Animation animation = animList.get(i); - anims.put(animation.getName(), animation); - } + animData = new AnimData(null, animations); + blenderContext.setAnimData(objectOMA, animData); + } - AnimControl control = new AnimControl(null); - control.setAnimations(anims); - node.addControl(control); - } - } - return node; - } + @Override + public Node apply(Node node, BlenderContext blenderContext) { + if (invalid) { + LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); + }// if invalid, animData will be null + if (animData != null) { + // INFO: constraints for this modifier are applied in the + // ObjectHelper when the whole object is loaded + ArrayList animList = animData.anims; + if (animList != null && animList.size() > 0) { + HashMap anims = new HashMap(); + for (int i = 0; i < animList.size(); ++i) { + Animation animation = animList.get(i); + anims.put(animation.getName(), animation); + } - @Override - public String getType() { - return Modifier.OBJECT_ANIMATION_MODIFIER_DATA; - } + AnimControl control = new AnimControl(null); + control.setAnimations(anims); + node.addControl(control); + } + } + return node; + } + + @Override + public String getType() { + return Modifier.OBJECT_ANIMATION_MODIFIER_DATA; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java index 5a61eb653..5ce04a70d 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java @@ -25,77 +25,76 @@ import java.util.logging.Logger; * @author Marcin Roguski (Kaelthas) */ /* package */class ParticlesModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); - - /** Loaded particles emitter. */ - private ParticleEmitter particleEmitter; - - /** - * This constructor reads the particles system structure and stores it in - * order to apply it later to the node. - * - * @param modifierStructure - * the structure of the modifier - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is throw wneh there are problems with the - * blender file - */ - public ParticlesModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { - if(this.validate(modifierStructure, blenderContext)) { - Pointer pParticleSystem = (Pointer) modifierStructure.getFieldValue("psys"); - if (pParticleSystem.isNotNull()) { - ParticlesHelper particlesHelper = blenderContext.getHelper(ParticlesHelper.class); - Structure particleSystem = pParticleSystem.fetchData(blenderContext.getInputStream()).get(0); - particleEmitter = particlesHelper.toParticleEmitter(particleSystem, blenderContext); - } - } - } + private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); - @Override - public Node apply(Node node, BlenderContext blenderContext) { - if(invalid) { - LOGGER.log(Level.WARNING, "Particles modifier is invalid! Cannot be applied to: {0}", node.getName()); - return node; - } - - MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); - ParticleEmitter emitter = particleEmitter.clone(); + /** Loaded particles emitter. */ + private ParticleEmitter particleEmitter; - // veryfying the alpha function for particles' texture - Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE; - char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1); - if (nameSuffix == 'B' || nameSuffix == 'N') { - alphaFunction = MaterialHelper.ALPHA_MASK_NONE; - } - // removing the type suffix from the name - emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1)); + /** + * This constructor reads the particles system structure and stores it in + * order to apply it later to the node. + * + * @param modifierStructure + * the structure of the modifier + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is throw wneh there are problems with the + * blender file + */ + public ParticlesModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { + if (this.validate(modifierStructure, blenderContext)) { + Pointer pParticleSystem = (Pointer) modifierStructure.getFieldValue("psys"); + if (pParticleSystem.isNotNull()) { + ParticlesHelper particlesHelper = blenderContext.getHelper(ParticlesHelper.class); + Structure particleSystem = pParticleSystem.fetchData(blenderContext.getInputStream()).get(0); + particleEmitter = particlesHelper.toParticleEmitter(particleSystem, blenderContext); + } + } + } - // applying emitter shape - EmitterShape emitterShape = emitter.getShape(); - List meshes = new ArrayList(); - for (Spatial spatial : node.getChildren()) { - if (spatial instanceof Geometry) { - Mesh mesh = ((Geometry) spatial).getMesh(); - if (mesh != null) { - meshes.add(mesh); - Material material = materialHelper.getParticlesMaterial( - ((Geometry) spatial).getMaterial(), alphaFunction, blenderContext); - emitter.setMaterial(material);// TODO: divide into several pieces - } - } - } - if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) { - ((EmitterMeshVertexShape) emitterShape).setMeshes(meshes); - } + @Override + public Node apply(Node node, BlenderContext blenderContext) { + if (invalid) { + LOGGER.log(Level.WARNING, "Particles modifier is invalid! Cannot be applied to: {0}", node.getName()); + return node; + } - node.attachChild(emitter); - return node; - } + MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + ParticleEmitter emitter = particleEmitter.clone(); - @Override - public String getType() { - return Modifier.PARTICLE_MODIFIER_DATA; - } + // veryfying the alpha function for particles' texture + Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE; + char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1); + if (nameSuffix == 'B' || nameSuffix == 'N') { + alphaFunction = MaterialHelper.ALPHA_MASK_NONE; + } + // removing the type suffix from the name + emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1)); + + // applying emitter shape + EmitterShape emitterShape = emitter.getShape(); + List meshes = new ArrayList(); + for (Spatial spatial : node.getChildren()) { + if (spatial instanceof Geometry) { + Mesh mesh = ((Geometry) spatial).getMesh(); + if (mesh != null) { + meshes.add(mesh); + Material material = materialHelper.getParticlesMaterial(((Geometry) spatial).getMaterial(), alphaFunction, blenderContext); + emitter.setMaterial(material);// TODO: divide into several pieces + } + } + } + if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) { + ((EmitterMeshVertexShape) emitterShape).setMeshes(meshes); + } + + node.attachChild(emitter); + return node; + } + + @Override + public String getType() { + return Modifier.PARTICLE_MODIFIER_DATA; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java index 2d4ccec7a..f0511ae48 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java @@ -68,317 +68,316 @@ import com.jme3.scene.plugins.blender.modifiers.ModifierHelper; * @author Marcin Roguski (Kaelthas) */ public class ObjectHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName()); - - protected static final int OBJECT_TYPE_EMPTY = 0; - protected static final int OBJECT_TYPE_MESH = 1; - protected static final int OBJECT_TYPE_CURVE = 2; - protected static final int OBJECT_TYPE_SURF = 3; - protected static final int OBJECT_TYPE_TEXT = 4; - protected static final int OBJECT_TYPE_METABALL = 5; - protected static final int OBJECT_TYPE_LAMP = 10; - protected static final int OBJECT_TYPE_CAMERA = 11; - protected static final int OBJECT_TYPE_WAVE = 21; - protected static final int OBJECT_TYPE_LATTICE = 22; - protected static final int OBJECT_TYPE_ARMATURE = 25; - - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in - * different blender versions. - * @param blenderVersion - * the version read from the blend file - * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not - */ - public ObjectHelper(String blenderVersion, boolean fixUpAxis) { - super(blenderVersion, fixUpAxis); - } - - /** - * This method reads the given structure and createn an object that represents the data. - * @param objectStructure - * the object's structure - * @param blenderContext - * the blender context - * @return blener's object representation - * @throws BlenderFileException - * an exception is thrown when the given data is inapropriate - */ - public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); - if(loadedResult != null) { - return loadedResult; - } - - blenderContext.pushParent(objectStructure); - - //get object data - int type = ((Number)objectStructure.getFieldValue("type")).intValue(); - String name = objectStructure.getName(); - LOGGER.log(Level.FINE, "Loading obejct: {0}", name); - - int restrictflag = ((Number)objectStructure.getFieldValue("restrictflag")).intValue(); - boolean visible = (restrictflag & 0x01) != 0; - Node result = null; - - Pointer pParent = (Pointer)objectStructure.getFieldValue("parent"); - Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); - if(parent == null && pParent.isNotNull()) { - Structure parentStructure = pParent.fetchData(blenderContext.getInputStream()).get(0); - parent = this.toObject(parentStructure, blenderContext); - } - - Transform t = this.getTransformation(objectStructure, blenderContext); - - try { - switch(type) { - case OBJECT_TYPE_EMPTY: - LOGGER.log(Level.FINE, "Importing empty."); - Node empty = new Node(name); - empty.setLocalTransform(t); - if(parent instanceof Node) { - ((Node) parent).attachChild(empty); - } - result = empty; - break; - case OBJECT_TYPE_MESH: - LOGGER.log(Level.FINE, "Importing mesh."); - Node node = new Node(name); - node.setCullHint(visible ? CullHint.Always : CullHint.Inherit); - - //reading mesh - MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); - Pointer pMesh = (Pointer)objectStructure.getFieldValue("data"); - List meshesArray = pMesh.fetchData(blenderContext.getInputStream()); - List geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext); - if (geometries != null){ - for(Geometry geometry : geometries) { + private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName()); + + protected static final int OBJECT_TYPE_EMPTY = 0; + protected static final int OBJECT_TYPE_MESH = 1; + protected static final int OBJECT_TYPE_CURVE = 2; + protected static final int OBJECT_TYPE_SURF = 3; + protected static final int OBJECT_TYPE_TEXT = 4; + protected static final int OBJECT_TYPE_METABALL = 5; + protected static final int OBJECT_TYPE_LAMP = 10; + protected static final int OBJECT_TYPE_CAMERA = 11; + protected static final int OBJECT_TYPE_WAVE = 21; + protected static final int OBJECT_TYPE_LATTICE = 22; + protected static final int OBJECT_TYPE_ARMATURE = 25; + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in + * different blender versions. + * @param blenderVersion + * the version read from the blend file + * @param fixUpAxis + * a variable that indicates if the Y asxis is the UP axis or not + */ + public ObjectHelper(String blenderVersion, boolean fixUpAxis) { + super(blenderVersion, fixUpAxis); + } + + /** + * This method reads the given structure and createn an object that represents the data. + * @param objectStructure + * the object's structure + * @param blenderContext + * the blender context + * @return blener's object representation + * @throws BlenderFileException + * an exception is thrown when the given data is inapropriate + */ + public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { + Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (loadedResult != null) { + return loadedResult; + } + + blenderContext.pushParent(objectStructure); + + // get object data + int type = ((Number) objectStructure.getFieldValue("type")).intValue(); + String name = objectStructure.getName(); + LOGGER.log(Level.FINE, "Loading obejct: {0}", name); + + int restrictflag = ((Number) objectStructure.getFieldValue("restrictflag")).intValue(); + boolean visible = (restrictflag & 0x01) != 0; + Node result = null; + + Pointer pParent = (Pointer) objectStructure.getFieldValue("parent"); + Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (parent == null && pParent.isNotNull()) { + Structure parentStructure = pParent.fetchData(blenderContext.getInputStream()).get(0); + parent = this.toObject(parentStructure, blenderContext); + } + + Transform t = this.getTransformation(objectStructure, blenderContext); + + try { + switch (type) { + case OBJECT_TYPE_EMPTY: + LOGGER.log(Level.FINE, "Importing empty."); + Node empty = new Node(name); + empty.setLocalTransform(t); + if (parent instanceof Node) { + ((Node) parent).attachChild(empty); + } + result = empty; + break; + case OBJECT_TYPE_MESH: + LOGGER.log(Level.FINE, "Importing mesh."); + Node node = new Node(name); + node.setCullHint(visible ? CullHint.Always : CullHint.Inherit); + + // reading mesh + MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); + Pointer pMesh = (Pointer) objectStructure.getFieldValue("data"); + List meshesArray = pMesh.fetchData(blenderContext.getInputStream()); + List geometries = meshHelper.toMesh(meshesArray.get(0), blenderContext); + if (geometries != null) { + for (Geometry geometry : geometries) { node.attachChild(geometry); } } - node.setLocalTransform(t); - - //setting the parent - if(parent instanceof Node) { - ((Node)parent).attachChild(node); - } - result = node; - break; - case OBJECT_TYPE_SURF: - case OBJECT_TYPE_CURVE: - LOGGER.log(Level.FINE, "Importing curve/nurb."); - Pointer pCurve = (Pointer)objectStructure.getFieldValue("data"); - if(pCurve.isNotNull()) { - CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class); - Structure curveData = pCurve.fetchData(blenderContext.getInputStream()).get(0); - List curves = curvesHelper.toCurve(curveData, blenderContext); - result = new Node(name); - for(Geometry curve : curves) { - ((Node)result).attachChild(curve); - } - ((Node)result).setLocalTransform(t); - } - break; - case OBJECT_TYPE_LAMP: - LOGGER.log(Level.FINE, "Importing lamp."); - Pointer pLamp = (Pointer)objectStructure.getFieldValue("data"); - if(pLamp.isNotNull()) { - LightHelper lightHelper = blenderContext.getHelper(LightHelper.class); - List lampsArray = pLamp.fetchData(blenderContext.getInputStream()); - LightNode light = lightHelper.toLight(lampsArray.get(0), blenderContext); - if(light!=null) { - light.setName(name); - light.setLocalTransform(t); - } - result = light; - } - break; - case OBJECT_TYPE_CAMERA: - Pointer pCamera = (Pointer)objectStructure.getFieldValue("data"); - if(pCamera.isNotNull()) { - CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class); - List camerasArray = pCamera.fetchData(blenderContext.getInputStream()); - CameraNode camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext); - camera.setName(name); - camera.setLocalTransform(t); - result = camera; - } - break; - case OBJECT_TYPE_ARMATURE: - //need to create an empty node to properly create parent-children relationships between nodes - Node armature = new Node(name); - armature.setLocalTransform(t); - armature.setUserData(ArmatureHelper.ARMETURE_NODE_MARKER, Boolean.TRUE); - - if(parent instanceof Node) { - ((Node)parent).attachChild(armature); - } - result = armature; - break; - default: - LOGGER.log(Level.WARNING, "Unknown object type: {0}", type); - } - } finally { - blenderContext.popParent(); - } - - if(result != null) { - result.updateModelBound();//I prefer do compute bounding box here than read it from the file - - blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result); - - //applying modifiers - LOGGER.log(Level.FINE, "Reading and applying object's modifiers."); - ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class); - Collection modifiers = modifierHelper.readModifiers(objectStructure, blenderContext); - for(Modifier modifier : modifiers) { - modifier.apply(result, blenderContext); - } - - //loading constraints connected with this object - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - constraintHelper.loadConstraints(objectStructure, blenderContext); - - //reading custom properties - if(blenderContext.getBlenderKey().isLoadObjectProperties()) { - Properties properties = this.loadProperties(objectStructure, blenderContext); - //the loaded property is a group property, so we need to get each value and set it to Spatial - if(result instanceof Spatial && properties != null && properties.getValue() != null) { - this.applyProperties((Spatial) result, properties); - } - } - } - return result; - } - - /** - * This method calculates local transformation for the object. Parentage is taken under consideration. - * @param objectStructure - * the object's structure - * @return objects transformation relative to its parent - */ - @SuppressWarnings("unchecked") - public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) { - //these are transformations in global space - DynamicArray loc = (DynamicArray)objectStructure.getFieldValue("loc"); - DynamicArray size = (DynamicArray)objectStructure.getFieldValue("size"); - DynamicArray rot = (DynamicArray)objectStructure.getFieldValue("rot"); - - //load parent inverse matrix - Pointer pParent = (Pointer) objectStructure.getFieldValue("parent"); - Matrix4f parentInv = pParent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv"); - - //create the global matrix (without the scale) - Matrix4f globalMatrix = new Matrix4f(); - globalMatrix.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue()); - globalMatrix.setRotationQuaternion(new Quaternion().fromAngles(rot.get(0).floatValue(), rot.get(1).floatValue(), rot.get(2).floatValue())); - //compute local matrix - Matrix4f localMatrix = parentInv.mult(globalMatrix); - - Vector3f translation = localMatrix.toTranslationVector(); - Quaternion rotation = localMatrix.toRotationQuat(); - Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue()); - - if(fixUpAxis) { - float y = translation.y; - translation.y = translation.z; - translation.z = -y; - - y = rotation.getY(); - float z = rotation.getZ(); - rotation.set(rotation.getX(), z, -y, rotation.getW()); - - y=scale.y; - scale.y = scale.z; - scale.z = y; - } - - //create the result - Transform t = new Transform(translation, rotation); - t.setScale(scale); - return t; - } - - /** - * This method returns the matrix of a given name for the given structure. - * The matrix is NOT transformed if Y axis is up - the raw data is loaded from the blender file. - * @param structure - * the structure with matrix data - * @param matrixName - * the name of the matrix - * @return the required matrix - */ - public Matrix4f getMatrix(Structure structure, String matrixName) { - return this.getMatrix(structure, matrixName, false); - } - - /** - * This method returns the matrix of a given name for the given structure. - * It takes up axis into consideration. - * @param structure - * the structure with matrix data - * @param matrixName - * the name of the matrix - * @return the required matrix - */ - @SuppressWarnings("unchecked") - public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) { - Matrix4f result = new Matrix4f(); - DynamicArray obmat = (DynamicArray)structure.getFieldValue(matrixName); - int rowAndColumnSize = Math.abs((int)Math.sqrt(obmat.getTotalSize()));//the matrix must be square - for(int i = 0; i < rowAndColumnSize; ++i) { - for(int j = 0; j < rowAndColumnSize; ++j) { - result.set(i, j, obmat.get(j, i).floatValue()); - } - } - if(applyFixUpAxis && fixUpAxis) { - Vector3f translation = result.toTranslationVector(); + node.setLocalTransform(t); + + // setting the parent + if (parent instanceof Node) { + ((Node) parent).attachChild(node); + } + result = node; + break; + case OBJECT_TYPE_SURF: + case OBJECT_TYPE_CURVE: + LOGGER.log(Level.FINE, "Importing curve/nurb."); + Pointer pCurve = (Pointer) objectStructure.getFieldValue("data"); + if (pCurve.isNotNull()) { + CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class); + Structure curveData = pCurve.fetchData(blenderContext.getInputStream()).get(0); + List curves = curvesHelper.toCurve(curveData, blenderContext); + result = new Node(name); + for (Geometry curve : curves) { + ((Node) result).attachChild(curve); + } + ((Node) result).setLocalTransform(t); + } + break; + case OBJECT_TYPE_LAMP: + LOGGER.log(Level.FINE, "Importing lamp."); + Pointer pLamp = (Pointer) objectStructure.getFieldValue("data"); + if (pLamp.isNotNull()) { + LightHelper lightHelper = blenderContext.getHelper(LightHelper.class); + List lampsArray = pLamp.fetchData(blenderContext.getInputStream()); + LightNode light = lightHelper.toLight(lampsArray.get(0), blenderContext); + if (light != null) { + light.setName(name); + light.setLocalTransform(t); + } + result = light; + } + break; + case OBJECT_TYPE_CAMERA: + Pointer pCamera = (Pointer) objectStructure.getFieldValue("data"); + if (pCamera.isNotNull()) { + CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class); + List camerasArray = pCamera.fetchData(blenderContext.getInputStream()); + CameraNode camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext); + camera.setName(name); + camera.setLocalTransform(t); + result = camera; + } + break; + case OBJECT_TYPE_ARMATURE: + // need to create an empty node to properly create parent-children relationships between nodes + Node armature = new Node(name); + armature.setLocalTransform(t); + armature.setUserData(ArmatureHelper.ARMETURE_NODE_MARKER, Boolean.TRUE); + + if (parent instanceof Node) { + ((Node) parent).attachChild(armature); + } + result = armature; + break; + default: + LOGGER.log(Level.WARNING, "Unknown object type: {0}", type); + } + } finally { + blenderContext.popParent(); + } + + if (result != null) { + result.updateModelBound();// I prefer do compute bounding box here than read it from the file + + blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result); + + // applying modifiers + LOGGER.log(Level.FINE, "Reading and applying object's modifiers."); + ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class); + Collection modifiers = modifierHelper.readModifiers(objectStructure, blenderContext); + for (Modifier modifier : modifiers) { + modifier.apply(result, blenderContext); + } + + // loading constraints connected with this object + ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + constraintHelper.loadConstraints(objectStructure, blenderContext); + + // reading custom properties + if (blenderContext.getBlenderKey().isLoadObjectProperties()) { + Properties properties = this.loadProperties(objectStructure, blenderContext); + // the loaded property is a group property, so we need to get each value and set it to Spatial + if (result instanceof Spatial && properties != null && properties.getValue() != null) { + this.applyProperties((Spatial) result, properties); + } + } + } + return result; + } + + /** + * This method calculates local transformation for the object. Parentage is taken under consideration. + * @param objectStructure + * the object's structure + * @return objects transformation relative to its parent + */ + @SuppressWarnings("unchecked") + public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) { + // these are transformations in global space + DynamicArray loc = (DynamicArray) objectStructure.getFieldValue("loc"); + DynamicArray size = (DynamicArray) objectStructure.getFieldValue("size"); + DynamicArray rot = (DynamicArray) objectStructure.getFieldValue("rot"); + + // load parent inverse matrix + Pointer pParent = (Pointer) objectStructure.getFieldValue("parent"); + Matrix4f parentInv = pParent.isNull() ? Matrix4f.IDENTITY : this.getMatrix(objectStructure, "parentinv"); + + // create the global matrix (without the scale) + Matrix4f globalMatrix = new Matrix4f(); + globalMatrix.setTranslation(loc.get(0).floatValue(), loc.get(1).floatValue(), loc.get(2).floatValue()); + globalMatrix.setRotationQuaternion(new Quaternion().fromAngles(rot.get(0).floatValue(), rot.get(1).floatValue(), rot.get(2).floatValue())); + // compute local matrix + Matrix4f localMatrix = parentInv.mult(globalMatrix); + + Vector3f translation = localMatrix.toTranslationVector(); + Quaternion rotation = localMatrix.toRotationQuat(); + Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue()); + + if (fixUpAxis) { + float y = translation.y; + translation.y = translation.z; + translation.z = -y; + + y = rotation.getY(); + float z = rotation.getZ(); + rotation.set(rotation.getX(), z, -y, rotation.getW()); + + y = scale.y; + scale.y = scale.z; + scale.z = y; + } + + // create the result + Transform t = new Transform(translation, rotation); + t.setScale(scale); + return t; + } + + /** + * This method returns the matrix of a given name for the given structure. + * The matrix is NOT transformed if Y axis is up - the raw data is loaded from the blender file. + * @param structure + * the structure with matrix data + * @param matrixName + * the name of the matrix + * @return the required matrix + */ + public Matrix4f getMatrix(Structure structure, String matrixName) { + return this.getMatrix(structure, matrixName, false); + } + + /** + * This method returns the matrix of a given name for the given structure. + * It takes up axis into consideration. + * @param structure + * the structure with matrix data + * @param matrixName + * the name of the matrix + * @return the required matrix + */ + @SuppressWarnings("unchecked") + public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) { + Matrix4f result = new Matrix4f(); + DynamicArray obmat = (DynamicArray) structure.getFieldValue(matrixName); + int rowAndColumnSize = Math.abs((int) Math.sqrt(obmat.getTotalSize()));// the matrix must be square + for (int i = 0; i < rowAndColumnSize; ++i) { + for (int j = 0; j < rowAndColumnSize; ++j) { + result.set(i, j, obmat.get(j, i).floatValue()); + } + } + if (applyFixUpAxis && fixUpAxis) { + Vector3f translation = result.toTranslationVector(); Quaternion rotation = result.toRotationQuat(); Vector3f scale = this.getScale(result); - - float y = translation.y; - translation.y = translation.z; - translation.z = -y; - - y = rotation.getY(); - float z = rotation.getZ(); - rotation.set(rotation.getX(), z, -y, rotation.getW()); - - y=scale.y; - scale.y = scale.z; - scale.z = y; - - result.loadIdentity(); - result.setTranslation(translation); - result.setRotationQuaternion(rotation); - result.setScale(scale); + + float y = translation.y; + translation.y = translation.z; + translation.z = -y; + + y = rotation.getY(); + float z = rotation.getZ(); + rotation.set(rotation.getX(), z, -y, rotation.getW()); + + y = scale.y; + scale.y = scale.z; + scale.z = y; + + result.loadIdentity(); + result.setTranslation(translation); + result.setRotationQuaternion(rotation); + result.setScale(scale); } - return result; - } - - /** - * This method returns the scale from the given matrix. - * - * @param matrix - * the transformation matrix - * @return the scale from the given matrix - */ - public Vector3f getScale(Matrix4f matrix) { - float scaleX = (float) Math.sqrt(matrix.m00 * matrix.m00 + matrix.m10 * matrix.m10 + matrix.m20 * matrix.m20); - float scaleY = (float) Math.sqrt(matrix.m01 * matrix.m01 + matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21); - float scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22); - return new Vector3f(scaleX, scaleY, scaleZ); - } - - @Override - public void clearState() { - fixUpAxis = false; - } - - @Override - public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - int lay = ((Number) structure.getFieldValue("lay")).intValue(); - return (lay & blenderContext.getBlenderKey().getLayersToLoad()) != 0 - && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0; - } + return result; + } + + /** + * This method returns the scale from the given matrix. + * + * @param matrix + * the transformation matrix + * @return the scale from the given matrix + */ + public Vector3f getScale(Matrix4f matrix) { + float scaleX = (float) Math.sqrt(matrix.m00 * matrix.m00 + matrix.m10 * matrix.m10 + matrix.m20 * matrix.m20); + float scaleY = (float) Math.sqrt(matrix.m01 * matrix.m01 + matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21); + float scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22); + return new Vector3f(scaleX, scaleY, scaleZ); + } + + @Override + public void clearState() { + fixUpAxis = false; + } + + @Override + public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { + int lay = ((Number) structure.getFieldValue("lay")).intValue(); + return (lay & blenderContext.getBlenderKey().getLayersToLoad()) != 0 && (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.OBJECTS) != 0; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/objects/Properties.java b/engine/src/blender/com/jme3/scene/plugins/blender/objects/Properties.java index 39018eb5a..d003f28ad 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/objects/Properties.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/objects/Properties.java @@ -17,364 +17,364 @@ import java.util.Map; * @author Marcin Roguski (Kaelthas) */ public class Properties implements Cloneable { - // property type - public static final int IDP_STRING = 0; - public static final int IDP_INT = 1; - public static final int IDP_FLOAT = 2; - public static final int IDP_ARRAY = 5; - public static final int IDP_GROUP = 6; - // public static final int IDP_ID = 7;//this is not implemented in blender (yet) - public static final int IDP_DOUBLE = 8; - // the following are valid for blender 2.5x+ - public static final int IDP_IDPARRAY = 9; - public static final int IDP_NUMTYPES = 10; + // property type + public static final int IDP_STRING = 0; + public static final int IDP_INT = 1; + public static final int IDP_FLOAT = 2; + public static final int IDP_ARRAY = 5; + public static final int IDP_GROUP = 6; + // public static final int IDP_ID = 7;//this is not implemented in blender (yet) + public static final int IDP_DOUBLE = 8; + // the following are valid for blender 2.5x+ + public static final int IDP_IDPARRAY = 9; + public static final int IDP_NUMTYPES = 10; - protected static final String RNA_PROPERTY_NAME = "_RNA_UI"; - /** Default name of the property (used if the name is not specified in blender file). */ - protected static final String DEFAULT_NAME = "Unnamed property"; + protected static final String RNA_PROPERTY_NAME = "_RNA_UI"; + /** Default name of the property (used if the name is not specified in blender file). */ + protected static final String DEFAULT_NAME = "Unnamed property"; - /** The name of the property. */ - private String name; - /** The type of the property. */ - private int type; - /** The subtype of the property. Defines the type of array's elements. */ - private int subType; - /** The value of the property. */ - private Object value; - /** The description of the property. */ - private String description; + /** The name of the property. */ + private String name; + /** The type of the property. */ + private int type; + /** The subtype of the property. Defines the type of array's elements. */ + private int subType; + /** The value of the property. */ + private Object value; + /** The description of the property. */ + private String description; - /** - * This method loads the property from the belnder file. - * @param idPropertyStructure - * the ID structure constining the property - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when the belnder file is somehow invalid - */ - public void load(Structure idPropertyStructure, BlenderContext blenderContext) throws BlenderFileException { - name = idPropertyStructure.getFieldValue("name").toString(); - if (name == null || name.length() == 0) { - name = DEFAULT_NAME; - } - subType = ((Number) idPropertyStructure.getFieldValue("subtype")).intValue(); - type = ((Number) idPropertyStructure.getFieldValue("type")).intValue(); + /** + * This method loads the property from the belnder file. + * @param idPropertyStructure + * the ID structure constining the property + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when the belnder file is somehow invalid + */ + public void load(Structure idPropertyStructure, BlenderContext blenderContext) throws BlenderFileException { + name = idPropertyStructure.getFieldValue("name").toString(); + if (name == null || name.length() == 0) { + name = DEFAULT_NAME; + } + subType = ((Number) idPropertyStructure.getFieldValue("subtype")).intValue(); + type = ((Number) idPropertyStructure.getFieldValue("type")).intValue(); - // reading the data - Structure data = (Structure) idPropertyStructure.getFieldValue("data"); - int len = ((Number) idPropertyStructure.getFieldValue("len")).intValue(); - switch (type) { - case IDP_STRING: { - Pointer pointer = (Pointer) data.getFieldValue("pointer"); - BlenderInputStream bis = blenderContext.getInputStream(); - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); - bis.setPosition(dataFileBlock.getBlockPosition()); - value = bis.readString(); - break; - } - case IDP_INT: - int intValue = ((Number) data.getFieldValue("val")).intValue(); - value = Integer.valueOf(intValue); - break; - case IDP_FLOAT: - int floatValue = ((Number) data.getFieldValue("val")).intValue(); - value = Float.valueOf(Float.intBitsToFloat(floatValue)); - break; - case IDP_ARRAY: { - Pointer pointer = (Pointer) data.getFieldValue("pointer"); - BlenderInputStream bis = blenderContext.getInputStream(); - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); - bis.setPosition(dataFileBlock.getBlockPosition()); - int elementAmount = dataFileBlock.getSize(); - switch (subType) { - case IDP_INT: - elementAmount /= 4; - int[] intList = new int[elementAmount]; - for (int i = 0; i < elementAmount; ++i) { - intList[i] = bis.readInt(); - } - value = intList; - break; - case IDP_FLOAT: - elementAmount /= 4; - float[] floatList = new float[elementAmount]; - for (int i = 0; i < elementAmount; ++i) { - floatList[i] = bis.readFloat(); - } - value = floatList; - break; - case IDP_DOUBLE: - elementAmount /= 8; - double[] doubleList = new double[elementAmount]; - for (int i = 0; i < elementAmount; ++i) { - doubleList[i] = bis.readDouble(); - } - value = doubleList; - break; - default: - throw new IllegalStateException("Invalid array subtype: " + subType); - } - } - case IDP_GROUP: - Structure group = (Structure) data.getFieldValue("group"); - List dataList = group.evaluateListBase(blenderContext); - List subProperties = new ArrayList(len); - for (Structure d : dataList) { - Properties properties = new Properties(); - properties.load(d, blenderContext); - subProperties.add(properties); - } - value = subProperties; - break; - case IDP_DOUBLE: - int doublePart1 = ((Number) data.getFieldValue("val")).intValue(); - int doublePart2 = ((Number) data.getFieldValue("val2")).intValue(); - long doubleVal = (long) doublePart2 << 32 | doublePart1; - value = Double.valueOf(Double.longBitsToDouble(doubleVal)); - break; - case IDP_IDPARRAY: { - Pointer pointer = (Pointer) data.getFieldValue("pointer"); - List arrays = pointer.fetchData(blenderContext.getInputStream()); - List result = new ArrayList(arrays.size()); - Properties temp = new Properties(); - for (Structure array : arrays) { - temp.load(array, blenderContext); - result.add(temp.value); - } - this.value = result; - break; - } - case IDP_NUMTYPES: - throw new UnsupportedOperationException(); - // case IDP_ID://not yet implemented in blender - // return null; - default: - throw new IllegalStateException("Unknown custom property type: " + type); - } - this.completeLoading(); - } + // reading the data + Structure data = (Structure) idPropertyStructure.getFieldValue("data"); + int len = ((Number) idPropertyStructure.getFieldValue("len")).intValue(); + switch (type) { + case IDP_STRING: { + Pointer pointer = (Pointer) data.getFieldValue("pointer"); + BlenderInputStream bis = blenderContext.getInputStream(); + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); + bis.setPosition(dataFileBlock.getBlockPosition()); + value = bis.readString(); + break; + } + case IDP_INT: + int intValue = ((Number) data.getFieldValue("val")).intValue(); + value = Integer.valueOf(intValue); + break; + case IDP_FLOAT: + int floatValue = ((Number) data.getFieldValue("val")).intValue(); + value = Float.valueOf(Float.intBitsToFloat(floatValue)); + break; + case IDP_ARRAY: { + Pointer pointer = (Pointer) data.getFieldValue("pointer"); + BlenderInputStream bis = blenderContext.getInputStream(); + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); + bis.setPosition(dataFileBlock.getBlockPosition()); + int elementAmount = dataFileBlock.getSize(); + switch (subType) { + case IDP_INT: + elementAmount /= 4; + int[] intList = new int[elementAmount]; + for (int i = 0; i < elementAmount; ++i) { + intList[i] = bis.readInt(); + } + value = intList; + break; + case IDP_FLOAT: + elementAmount /= 4; + float[] floatList = new float[elementAmount]; + for (int i = 0; i < elementAmount; ++i) { + floatList[i] = bis.readFloat(); + } + value = floatList; + break; + case IDP_DOUBLE: + elementAmount /= 8; + double[] doubleList = new double[elementAmount]; + for (int i = 0; i < elementAmount; ++i) { + doubleList[i] = bis.readDouble(); + } + value = doubleList; + break; + default: + throw new IllegalStateException("Invalid array subtype: " + subType); + } + } + case IDP_GROUP: + Structure group = (Structure) data.getFieldValue("group"); + List dataList = group.evaluateListBase(blenderContext); + List subProperties = new ArrayList(len); + for (Structure d : dataList) { + Properties properties = new Properties(); + properties.load(d, blenderContext); + subProperties.add(properties); + } + value = subProperties; + break; + case IDP_DOUBLE: + int doublePart1 = ((Number) data.getFieldValue("val")).intValue(); + int doublePart2 = ((Number) data.getFieldValue("val2")).intValue(); + long doubleVal = (long) doublePart2 << 32 | doublePart1; + value = Double.valueOf(Double.longBitsToDouble(doubleVal)); + break; + case IDP_IDPARRAY: { + Pointer pointer = (Pointer) data.getFieldValue("pointer"); + List arrays = pointer.fetchData(blenderContext.getInputStream()); + List result = new ArrayList(arrays.size()); + Properties temp = new Properties(); + for (Structure array : arrays) { + temp.load(array, blenderContext); + result.add(temp.value); + } + this.value = result; + break; + } + case IDP_NUMTYPES: + throw new UnsupportedOperationException(); + // case IDP_ID://not yet implemented in blender + // return null; + default: + throw new IllegalStateException("Unknown custom property type: " + type); + } + this.completeLoading(); + } - /** - * This method returns the name of the property. - * @return the name of the property - */ - public String getName() { - return name; - } + /** + * This method returns the name of the property. + * @return the name of the property + */ + public String getName() { + return name; + } - /** - * This method returns the description of the property. - * @return the description of the property - */ - public String getDescription() { - return description; - } + /** + * This method returns the description of the property. + * @return the description of the property + */ + public String getDescription() { + return description; + } - /** - * This method returns the type of the property. - * @return the type of the property - */ - public int getType() { - return type; - } + /** + * This method returns the type of the property. + * @return the type of the property + */ + public int getType() { + return type; + } - /** - * This method returns the value of the property. - * The type of the value depends on the type of the property. - * @return the value of the property - */ - public Object getValue() { - return value; - } - - /** - * @return the names of properties that are stored withing this property - * (assuming this property is of IDP_GROUP type) - */ - @SuppressWarnings("unchecked") - public List getSubPropertiesNames() { - List result = null; - if(this.type == IDP_GROUP) { - List properties = (List)this.value; - if(properties != null && properties.size() > 0) { - result = new ArrayList(properties.size()); - for(Properties property : properties) { - result.add(property.getName()); - } - } - } - return result; - } - - /** - * This method returns the same as getValue if the current property is of - * other type than IDP_GROUP and its name matches 'propertyName' param. If - * this property is a group property the method tries to find subproperty - * value of the given name. The first found value is returnes os use this - * method wisely. If no property of a given name is foung - null - * is returned. - * - * @param propertyName - * the name of the property - * @return found property value or null - */ - @SuppressWarnings("unchecked") - public Object findValue(String propertyName) { - if (name.equals(propertyName)) { - return value; - } else { - if (type == IDP_GROUP) { - List props = (List) value; - for (Properties p : props) { - Object v = p.findValue(propertyName); - if (v != null) { - return v; - } - } - } - } - return null; - } + /** + * This method returns the value of the property. + * The type of the value depends on the type of the property. + * @return the value of the property + */ + public Object getValue() { + return value; + } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - this.append(sb, new StringBuilder()); - return sb.toString(); - } + /** + * @return the names of properties that are stored withing this property + * (assuming this property is of IDP_GROUP type) + */ + @SuppressWarnings("unchecked") + public List getSubPropertiesNames() { + List result = null; + if (this.type == IDP_GROUP) { + List properties = (List) this.value; + if (properties != null && properties.size() > 0) { + result = new ArrayList(properties.size()); + for (Properties property : properties) { + result.add(property.getName()); + } + } + } + return result; + } - /** - * This method appends the data of the property to the given string buffer. - * @param sb - * string buffer - * @param indent - * indent buffer - */ - @SuppressWarnings("unchecked") - private void append(StringBuilder sb, StringBuilder indent) { - sb.append(indent).append("name: ").append(name).append("\n\r"); - sb.append(indent).append("type: ").append(type).append("\n\r"); - sb.append(indent).append("subType: ").append(subType).append("\n\r"); - sb.append(indent).append("description: ").append(description).append("\n\r"); - indent.append('\t'); - sb.append(indent).append("value: "); - if (value instanceof Properties) { - ((Properties) value).append(sb, indent); - } else if (value instanceof List) { - for (Object v : (List) value) { - if (v instanceof Properties) { - sb.append(indent).append("{\n\r"); - indent.append('\t'); - ((Properties) v).append(sb, indent); - indent.deleteCharAt(indent.length() - 1); - sb.append(indent).append("}\n\r"); - } else { - sb.append(v); - } - } - } else { - sb.append(value); - } - sb.append("\n\r"); - indent.deleteCharAt(indent.length() - 1); - } + /** + * This method returns the same as getValue if the current property is of + * other type than IDP_GROUP and its name matches 'propertyName' param. If + * this property is a group property the method tries to find subproperty + * value of the given name. The first found value is returnes os use this + * method wisely. If no property of a given name is foung - null + * is returned. + * + * @param propertyName + * the name of the property + * @return found property value or null + */ + @SuppressWarnings("unchecked") + public Object findValue(String propertyName) { + if (name.equals(propertyName)) { + return value; + } else { + if (type == IDP_GROUP) { + List props = (List) value; + for (Properties p : props) { + Object v = p.findValue(propertyName); + if (v != null) { + return v; + } + } + } + } + return null; + } - /** - * This method should be called after the properties loading. - * It loads the properties from the _RNA_UI property and removes this property from the - * result list. - */ - @SuppressWarnings("unchecked") - protected void completeLoading() { - if (this.type == IDP_GROUP) { - List groupProperties = (List) this.value; - Properties rnaUI = null; - for (Properties properties : groupProperties) { - if (properties.name.equals(RNA_PROPERTY_NAME) && properties.type == IDP_GROUP) { - rnaUI = properties; - break; - } - } - if (rnaUI != null) { - // removing the RNA from the result list - groupProperties.remove(rnaUI); + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + this.append(sb, new StringBuilder()); + return sb.toString(); + } - // loading the descriptions - Map descriptions = new HashMap(groupProperties.size()); - List propertiesRNA = (List) rnaUI.value; - for (Properties properties : propertiesRNA) { - String name = properties.name; - String description = null; - List rnaData = (List) properties.value; - for (Properties rna : rnaData) { - if ("description".equalsIgnoreCase(rna.name)) { - description = (String) rna.value; - break; - } - } - descriptions.put(name, description); - } + /** + * This method appends the data of the property to the given string buffer. + * @param sb + * string buffer + * @param indent + * indent buffer + */ + @SuppressWarnings("unchecked") + private void append(StringBuilder sb, StringBuilder indent) { + sb.append(indent).append("name: ").append(name).append("\n\r"); + sb.append(indent).append("type: ").append(type).append("\n\r"); + sb.append(indent).append("subType: ").append(subType).append("\n\r"); + sb.append(indent).append("description: ").append(description).append("\n\r"); + indent.append('\t'); + sb.append(indent).append("value: "); + if (value instanceof Properties) { + ((Properties) value).append(sb, indent); + } else if (value instanceof List) { + for (Object v : (List) value) { + if (v instanceof Properties) { + sb.append(indent).append("{\n\r"); + indent.append('\t'); + ((Properties) v).append(sb, indent); + indent.deleteCharAt(indent.length() - 1); + sb.append(indent).append("}\n\r"); + } else { + sb.append(v); + } + } + } else { + sb.append(value); + } + sb.append("\n\r"); + indent.deleteCharAt(indent.length() - 1); + } - // applying the descriptions - for (Properties properties : groupProperties) { - properties.description = descriptions.get(properties.name); - } - } - } - } + /** + * This method should be called after the properties loading. + * It loads the properties from the _RNA_UI property and removes this property from the + * result list. + */ + @SuppressWarnings("unchecked") + protected void completeLoading() { + if (this.type == IDP_GROUP) { + List groupProperties = (List) this.value; + Properties rnaUI = null; + for (Properties properties : groupProperties) { + if (properties.name.equals(RNA_PROPERTY_NAME) && properties.type == IDP_GROUP) { + rnaUI = properties; + break; + } + } + if (rnaUI != null) { + // removing the RNA from the result list + groupProperties.remove(rnaUI); - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (description == null ? 0 : description.hashCode()); - result = prime * result + (name == null ? 0 : name.hashCode()); - result = prime * result + subType; - result = prime * result + type; - result = prime * result + (value == null ? 0 : value.hashCode()); - return result; - } + // loading the descriptions + Map descriptions = new HashMap(groupProperties.size()); + List propertiesRNA = (List) rnaUI.value; + for (Properties properties : propertiesRNA) { + String name = properties.name; + String description = null; + List rnaData = (List) properties.value; + for (Properties rna : rnaData) { + if ("description".equalsIgnoreCase(rna.name)) { + description = (String) rna.value; + break; + } + } + descriptions.put(name, description); + } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (this.getClass() != obj.getClass()) { - return false; - } - Properties other = (Properties) obj; - if (description == null) { - if (other.description != null) { - return false; - } - } else if (!description.equals(other.description)) { - return false; - } - if (name == null) { - if (other.name != null) { - return false; - } - } else if (!name.equals(other.name)) { - return false; - } - if (subType != other.subType) { - return false; - } - if (type != other.type) { - return false; - } - if (value == null) { - if (other.value != null) { - return false; - } - } else if (!value.equals(other.value)) { - return false; - } - return true; - } + // applying the descriptions + for (Properties properties : groupProperties) { + properties.description = descriptions.get(properties.name); + } + } + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (description == null ? 0 : description.hashCode()); + result = prime * result + (name == null ? 0 : name.hashCode()); + result = prime * result + subType; + result = prime * result + type; + result = prime * result + (value == null ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (this.getClass() != obj.getClass()) { + return false; + } + Properties other = (Properties) obj; + if (description == null) { + if (other.description != null) { + return false; + } + } else if (!description.equals(other.description)) { + return false; + } + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (subType != other.subType) { + return false; + } + if (type != other.type) { + return false; + } + if (value == null) { + if (other.value != null) { + return false; + } + } else if (!value.equals(other.value)) { + return false; + } + return true; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java index de1252a34..784197e49 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java @@ -18,179 +18,179 @@ import com.jme3.scene.plugins.blender.file.Structure; import java.util.logging.Logger; public class ParticlesHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(ParticlesHelper.class.getName()); - - // part->type - public static final int PART_EMITTER = 0; - public static final int PART_REACTOR = 1; - public static final int PART_HAIR = 2; - public static final int PART_FLUID = 3; - - // part->flag - public static final int PART_REACT_STA_END =1; - public static final int PART_REACT_MULTIPLE =2; - public static final int PART_LOOP =4; - //public static final int PART_LOOP_INSTANT =8; - public static final int PART_HAIR_GEOMETRY =16; - public static final int PART_UNBORN =32; //show unborn particles - public static final int PART_DIED =64; //show died particles - public static final int PART_TRAND =128; - public static final int PART_EDISTR =256; // particle/face from face areas - public static final int PART_STICKY =512; //collided particles can stick to collider - public static final int PART_DIE_ON_COL =1<<12; - public static final int PART_SIZE_DEFL =1<<13; // swept sphere deflections - public static final int PART_ROT_DYN =1<<14; // dynamic rotation - public static final int PART_SIZEMASS =1<<16; - public static final int PART_ABS_LENGTH =1<<15; - public static final int PART_ABS_TIME =1<<17; - public static final int PART_GLOB_TIME =1<<18; - public static final int PART_BOIDS_2D =1<<19; - public static final int PART_BRANCHING =1<<20; - public static final int PART_ANIM_BRANCHING =1<<21; - public static final int PART_SELF_EFFECT =1<<22; - public static final int PART_SYMM_BRANCHING =1<<24; - public static final int PART_HAIR_BSPLINE =1024; - public static final int PART_GRID_INVERT =1<<26; - public static final int PART_CHILD_EFFECT =1<<27; - public static final int PART_CHILD_SEAMS =1<<28; - public static final int PART_CHILD_RENDER =1<<29; - public static final int PART_CHILD_GUIDE =1<<30; - - // part->from - public static final int PART_FROM_VERT =0; - public static final int PART_FROM_FACE =1; - public static final int PART_FROM_VOLUME =2; - public static final int PART_FROM_PARTICLE =3; - public static final int PART_FROM_CHILD =4; - - // part->phystype - public static final int PART_PHYS_NO = 0; - public static final int PART_PHYS_NEWTON= 1; - public static final int PART_PHYS_KEYED = 2; - public static final int PART_PHYS_BOIDS = 3; - - // part->draw_as - public static final int PART_DRAW_NOT = 0; - public static final int PART_DRAW_DOT = 1; - public static final int PART_DRAW_CIRC = 2; - public static final int PART_DRAW_CROSS = 3; - public static final int PART_DRAW_AXIS = 4; - public static final int PART_DRAW_LINE = 5; - public static final int PART_DRAW_PATH = 6; - public static final int PART_DRAW_OB = 7; - public static final int PART_DRAW_GR = 8; - public static final int PART_DRAW_BB = 9; - - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in - * different blender versions. - * @param blenderVersion - * the version read from the blend file - * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not - */ - public ParticlesHelper(String blenderVersion, boolean fixUpAxis) { - super(blenderVersion, fixUpAxis); - } - - @SuppressWarnings("unchecked") - public ParticleEmitter toParticleEmitter(Structure particleSystem, BlenderContext blenderContext) throws BlenderFileException { - ParticleEmitter result = null; - Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part"); - if(pParticleSettings.isNotNull()) { - Structure particleSettings = pParticleSettings.fetchData(blenderContext.getInputStream()).get(0); - - int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue(); - - //draw type will be stored temporarily in the name (it is used during modifier applying operation) - int drawAs = ((Number)particleSettings.getFieldValue("draw_as")).intValue(); - char nameSuffix;//P - point, L - line, N - None, B - Bilboard - switch(drawAs) { - case PART_DRAW_NOT: - nameSuffix = 'N'; - totPart = 0;//no need to generate particles in this case - break; - case PART_DRAW_BB: - nameSuffix = 'B'; - break; - case PART_DRAW_OB: - case PART_DRAW_GR: - nameSuffix = 'P'; - LOGGER.warning("Neither object nor group particles supported yet! Using point representation instead!");//TODO: support groups and aobjects - break; - case PART_DRAW_LINE: - nameSuffix = 'L'; - LOGGER.warning("Lines not yet supported! Using point representation instead!");//TODO: support lines - default://all others are rendered as points in blender - nameSuffix = 'P'; - } - result = new ParticleEmitter(particleSettings.getName()+nameSuffix, Type.Triangle, totPart); - if(nameSuffix=='N') { - return result;//no need to set anything else - } - - //setting the emitters shape (the shapes meshes will be set later during modifier applying operation) - int from = ((Number)particleSettings.getFieldValue("from")).intValue(); - switch(from) { - case PART_FROM_VERT: - result.setShape(new EmitterMeshVertexShape()); - break; - case PART_FROM_FACE: - result.setShape(new EmitterMeshFaceShape()); - break; - case PART_FROM_VOLUME: - result.setShape(new EmitterMeshConvexHullShape()); - break; - default: - LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter: " + from + ')'); - } - - //reading acceleration - DynamicArray acc = (DynamicArray) particleSettings.getFieldValue("acc"); - result.setGravity(-acc.get(0).floatValue(), -acc.get(1).floatValue(), -acc.get(2).floatValue()); - - //setting the colors - result.setEndColor(new ColorRGBA(1f, 1f, 1f, 1f)); - result.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f)); - - //reading size - float sizeFactor = nameSuffix=='B' ? 1.0f : 0.3f; - float size = ((Number)particleSettings.getFieldValue("size")).floatValue() * sizeFactor; - result.setStartSize(size); - result.setEndSize(size); - - //reading lifetime - int fps = blenderContext.getBlenderKey().getFps(); - float lifetime = ((Number)particleSettings.getFieldValue("lifetime")).floatValue() / fps; - float randlife = ((Number)particleSettings.getFieldValue("randlife")).floatValue() / fps; - result.setLowLife(lifetime * (1.0f - randlife)); - result.setHighLife(lifetime); - - //preparing influencer - ParticleInfluencer influencer; - int phystype = ((Number)particleSettings.getFieldValue("phystype")).intValue(); - switch(phystype) { - case PART_PHYS_NEWTON: - influencer = new NewtonianParticleInfluencer(); - ((NewtonianParticleInfluencer)influencer).setNormalVelocity(((Number)particleSettings.getFieldValue("normfac")).floatValue()); - ((NewtonianParticleInfluencer)influencer).setVelocityVariation(((Number)particleSettings.getFieldValue("randfac")).floatValue()); - ((NewtonianParticleInfluencer)influencer).setSurfaceTangentFactor(((Number)particleSettings.getFieldValue("tanfac")).floatValue()); - ((NewtonianParticleInfluencer)influencer).setSurfaceTangentRotation(((Number)particleSettings.getFieldValue("tanphase")).floatValue()); - break; - case PART_PHYS_BOIDS: - case PART_PHYS_KEYED://TODO: support other influencers - LOGGER.warning("Boids and Keyed particles physic not yet supported! Empty influencer used!"); - case PART_PHYS_NO: - default: - influencer = new EmptyParticleInfluencer(); - } - result.setParticleInfluencer(influencer); - } - return result; - } - - @Override - public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - return true; - } + private static final Logger LOGGER = Logger.getLogger(ParticlesHelper.class.getName()); + + // part->type + public static final int PART_EMITTER = 0; + public static final int PART_REACTOR = 1; + public static final int PART_HAIR = 2; + public static final int PART_FLUID = 3; + + // part->flag + public static final int PART_REACT_STA_END = 1; + public static final int PART_REACT_MULTIPLE = 2; + public static final int PART_LOOP = 4; + // public static final int PART_LOOP_INSTANT =8; + public static final int PART_HAIR_GEOMETRY = 16; + public static final int PART_UNBORN = 32; // show unborn particles + public static final int PART_DIED = 64; // show died particles + public static final int PART_TRAND = 128; + public static final int PART_EDISTR = 256; // particle/face from face areas + public static final int PART_STICKY = 512; // collided particles can stick to collider + public static final int PART_DIE_ON_COL = 1 << 12; + public static final int PART_SIZE_DEFL = 1 << 13; // swept sphere deflections + public static final int PART_ROT_DYN = 1 << 14; // dynamic rotation + public static final int PART_SIZEMASS = 1 << 16; + public static final int PART_ABS_LENGTH = 1 << 15; + public static final int PART_ABS_TIME = 1 << 17; + public static final int PART_GLOB_TIME = 1 << 18; + public static final int PART_BOIDS_2D = 1 << 19; + public static final int PART_BRANCHING = 1 << 20; + public static final int PART_ANIM_BRANCHING = 1 << 21; + public static final int PART_SELF_EFFECT = 1 << 22; + public static final int PART_SYMM_BRANCHING = 1 << 24; + public static final int PART_HAIR_BSPLINE = 1024; + public static final int PART_GRID_INVERT = 1 << 26; + public static final int PART_CHILD_EFFECT = 1 << 27; + public static final int PART_CHILD_SEAMS = 1 << 28; + public static final int PART_CHILD_RENDER = 1 << 29; + public static final int PART_CHILD_GUIDE = 1 << 30; + + // part->from + public static final int PART_FROM_VERT = 0; + public static final int PART_FROM_FACE = 1; + public static final int PART_FROM_VOLUME = 2; + public static final int PART_FROM_PARTICLE = 3; + public static final int PART_FROM_CHILD = 4; + + // part->phystype + public static final int PART_PHYS_NO = 0; + public static final int PART_PHYS_NEWTON = 1; + public static final int PART_PHYS_KEYED = 2; + public static final int PART_PHYS_BOIDS = 3; + + // part->draw_as + public static final int PART_DRAW_NOT = 0; + public static final int PART_DRAW_DOT = 1; + public static final int PART_DRAW_CIRC = 2; + public static final int PART_DRAW_CROSS = 3; + public static final int PART_DRAW_AXIS = 4; + public static final int PART_DRAW_LINE = 5; + public static final int PART_DRAW_PATH = 6; + public static final int PART_DRAW_OB = 7; + public static final int PART_DRAW_GR = 8; + public static final int PART_DRAW_BB = 9; + + /** + * This constructor parses the given blender version and stores the result. Some functionalities may differ in + * different blender versions. + * @param blenderVersion + * the version read from the blend file + * @param fixUpAxis + * a variable that indicates if the Y asxis is the UP axis or not + */ + public ParticlesHelper(String blenderVersion, boolean fixUpAxis) { + super(blenderVersion, fixUpAxis); + } + + @SuppressWarnings("unchecked") + public ParticleEmitter toParticleEmitter(Structure particleSystem, BlenderContext blenderContext) throws BlenderFileException { + ParticleEmitter result = null; + Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part"); + if (pParticleSettings.isNotNull()) { + Structure particleSettings = pParticleSettings.fetchData(blenderContext.getInputStream()).get(0); + + int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue(); + + // draw type will be stored temporarily in the name (it is used during modifier applying operation) + int drawAs = ((Number) particleSettings.getFieldValue("draw_as")).intValue(); + char nameSuffix;// P - point, L - line, N - None, B - Bilboard + switch (drawAs) { + case PART_DRAW_NOT: + nameSuffix = 'N'; + totPart = 0;// no need to generate particles in this case + break; + case PART_DRAW_BB: + nameSuffix = 'B'; + break; + case PART_DRAW_OB: + case PART_DRAW_GR: + nameSuffix = 'P'; + LOGGER.warning("Neither object nor group particles supported yet! Using point representation instead!");// TODO: support groups and aobjects + break; + case PART_DRAW_LINE: + nameSuffix = 'L'; + LOGGER.warning("Lines not yet supported! Using point representation instead!");// TODO: support lines + default:// all others are rendered as points in blender + nameSuffix = 'P'; + } + result = new ParticleEmitter(particleSettings.getName() + nameSuffix, Type.Triangle, totPart); + if (nameSuffix == 'N') { + return result;// no need to set anything else + } + + // setting the emitters shape (the shapes meshes will be set later during modifier applying operation) + int from = ((Number) particleSettings.getFieldValue("from")).intValue(); + switch (from) { + case PART_FROM_VERT: + result.setShape(new EmitterMeshVertexShape()); + break; + case PART_FROM_FACE: + result.setShape(new EmitterMeshFaceShape()); + break; + case PART_FROM_VOLUME: + result.setShape(new EmitterMeshConvexHullShape()); + break; + default: + LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter: " + from + ')'); + } + + // reading acceleration + DynamicArray acc = (DynamicArray) particleSettings.getFieldValue("acc"); + result.setGravity(-acc.get(0).floatValue(), -acc.get(1).floatValue(), -acc.get(2).floatValue()); + + // setting the colors + result.setEndColor(new ColorRGBA(1f, 1f, 1f, 1f)); + result.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f)); + + // reading size + float sizeFactor = nameSuffix == 'B' ? 1.0f : 0.3f; + float size = ((Number) particleSettings.getFieldValue("size")).floatValue() * sizeFactor; + result.setStartSize(size); + result.setEndSize(size); + + // reading lifetime + int fps = blenderContext.getBlenderKey().getFps(); + float lifetime = ((Number) particleSettings.getFieldValue("lifetime")).floatValue() / fps; + float randlife = ((Number) particleSettings.getFieldValue("randlife")).floatValue() / fps; + result.setLowLife(lifetime * (1.0f - randlife)); + result.setHighLife(lifetime); + + // preparing influencer + ParticleInfluencer influencer; + int phystype = ((Number) particleSettings.getFieldValue("phystype")).intValue(); + switch (phystype) { + case PART_PHYS_NEWTON: + influencer = new NewtonianParticleInfluencer(); + ((NewtonianParticleInfluencer) influencer).setNormalVelocity(((Number) particleSettings.getFieldValue("normfac")).floatValue()); + ((NewtonianParticleInfluencer) influencer).setVelocityVariation(((Number) particleSettings.getFieldValue("randfac")).floatValue()); + ((NewtonianParticleInfluencer) influencer).setSurfaceTangentFactor(((Number) particleSettings.getFieldValue("tanfac")).floatValue()); + ((NewtonianParticleInfluencer) influencer).setSurfaceTangentRotation(((Number) particleSettings.getFieldValue("tanphase")).floatValue()); + break; + case PART_PHYS_BOIDS: + case PART_PHYS_KEYED:// TODO: support other influencers + LOGGER.warning("Boids and Keyed particles physic not yet supported! Empty influencer used!"); + case PART_PHYS_NO: + default: + influencer = new EmptyParticleInfluencer(); + } + result.setParticleInfluencer(influencer); + } + return result; + } + + @Override + public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { + return true; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java index 744be1864..edd32c24e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/ColorBand.java @@ -48,310 +48,310 @@ import java.util.logging.Logger; * @author Marcin Roguski (Kaelthas) */ public class ColorBand { - private static final Logger LOGGER = Logger.getLogger(ColorBand.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ColorBand.class.getName()); - // interpolation types - public static final int IPO_LINEAR = 0; - public static final int IPO_EASE = 1; - public static final int IPO_BSPLINE = 2; - public static final int IPO_CARDINAL = 3; - public static final int IPO_CONSTANT = 4; + // interpolation types + public static final int IPO_LINEAR = 0; + public static final int IPO_EASE = 1; + public static final int IPO_BSPLINE = 2; + public static final int IPO_CARDINAL = 3; + public static final int IPO_CONSTANT = 4; - private int cursorsAmount, ipoType; - private ColorBandData[] data; + private int cursorsAmount, ipoType; + private ColorBandData[] data; - /** - * Constructor. Loads the data from the given structure. - * @param tex - * @param blenderContext - */ - @SuppressWarnings("unchecked") - public ColorBand(Structure tex, BlenderContext blenderContext) { - int flag = ((Number) tex.getFieldValue("flag")).intValue(); - if ((flag & GeneratedTexture.TEX_COLORBAND) != 0) { - Pointer pColorband = (Pointer) tex.getFieldValue("coba"); - try { - Structure colorbandStructure = pColorband.fetchData(blenderContext.getInputStream()).get(0); - this.cursorsAmount = ((Number) colorbandStructure.getFieldValue("tot")).intValue(); - this.ipoType = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue(); - this.data = new ColorBandData[this.cursorsAmount]; - DynamicArray data = (DynamicArray) colorbandStructure.getFieldValue("data"); - for (int i = 0; i < this.cursorsAmount; ++i) { - this.data[i] = new ColorBandData(data.get(i)); - } - } catch (BlenderFileException e) { - LOGGER.log(Level.WARNING, "Cannot fetch the colorband structure. The reason: {0}", e.getLocalizedMessage()); - } - } - } + /** + * Constructor. Loads the data from the given structure. + * @param tex + * @param blenderContext + */ + @SuppressWarnings("unchecked") + public ColorBand(Structure tex, BlenderContext blenderContext) { + int flag = ((Number) tex.getFieldValue("flag")).intValue(); + if ((flag & GeneratedTexture.TEX_COLORBAND) != 0) { + Pointer pColorband = (Pointer) tex.getFieldValue("coba"); + try { + Structure colorbandStructure = pColorband.fetchData(blenderContext.getInputStream()).get(0); + this.cursorsAmount = ((Number) colorbandStructure.getFieldValue("tot")).intValue(); + this.ipoType = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue(); + this.data = new ColorBandData[this.cursorsAmount]; + DynamicArray data = (DynamicArray) colorbandStructure.getFieldValue("data"); + for (int i = 0; i < this.cursorsAmount; ++i) { + this.data[i] = new ColorBandData(data.get(i)); + } + } catch (BlenderFileException e) { + LOGGER.log(Level.WARNING, "Cannot fetch the colorband structure. The reason: {0}", e.getLocalizedMessage()); + } + } + } + + /** + * This method determines if the colorband has any transparencies or is not + * transparent at all. + * + * @return true if the colorband has transparencies and false + * otherwise + */ + public boolean hasTransparencies() { + if (data != null) { + for (ColorBandData colorBandData : data) { + if (colorBandData.a < 1.0f) { + return true; + } + } + } + return false; + } - /** - * This method determines if the colorband has any transparencies or is not - * transparent at all. - * - * @return true if the colorband has transparencies and false - * otherwise - */ - public boolean hasTransparencies() { - if (data != null) { - for (ColorBandData colorBandData : data) { - if (colorBandData.a < 1.0f) { - return true; - } - } - } - return false; - } + /** + * This method computes the values of the colorband. + * + * @return an array of 1001 elements and each element is float[4] object + * containing rgba values + */ + public float[][] computeValues() { + float[][] result = null; + if (data != null) { + result = new float[1001][4];// 1001 - amount of possible cursor + // positions; 4 = [r, g, b, a] - /** - * This method computes the values of the colorband. - * - * @return an array of 1001 elements and each element is float[4] object - * containing rgba values - */ - public float[][] computeValues() { - float[][] result = null; - if (data != null) { - result = new float[1001][4];// 1001 - amount of possible cursor - // positions; 4 = [r, g, b, a] + if (data.length == 1) {// special case; use only one color for all + // types of colorband interpolation + for (int i = 0; i < result.length; ++i) { + result[i][0] = data[0].r; + result[i][1] = data[0].g; + result[i][2] = data[0].b; + result[i][3] = data[0].a; + } + } else { + int currentCursor = 0; + ColorBandData currentData = data[0]; + ColorBandData nextData = data[0]; + switch (ipoType) { + case ColorBand.IPO_LINEAR: + float rDiff = 0, + gDiff = 0, + bDiff = 0, + aDiff = 0, + posDiff; + for (int i = 0; i < result.length; ++i) { + posDiff = i - currentData.pos; + result[i][0] = currentData.r + rDiff * posDiff; + result[i][1] = currentData.g + gDiff * posDiff; + result[i][2] = currentData.b + bDiff * posDiff; + result[i][3] = currentData.a + aDiff * posDiff; + if (nextData.pos == i) { + currentData = data[currentCursor++]; + if (currentCursor < data.length) { + nextData = data[currentCursor]; + // calculate differences + int d = nextData.pos - currentData.pos; + rDiff = (nextData.r - currentData.r) / d; + gDiff = (nextData.g - currentData.g) / d; + bDiff = (nextData.b - currentData.b) / d; + aDiff = (nextData.a - currentData.a) / d; + } else { + rDiff = gDiff = bDiff = aDiff = 0; + } + } + } + break; + case ColorBand.IPO_BSPLINE: + case ColorBand.IPO_CARDINAL: + Map cbDataMap = new TreeMap(); + for (int i = 0; i < data.length; ++i) { + cbDataMap.put(Integer.valueOf(i), data[i]); + } - if (data.length == 1) {// special case; use only one color for all - // types of colorband interpolation - for (int i = 0; i < result.length; ++i) { - result[i][0] = data[0].r; - result[i][1] = data[0].g; - result[i][2] = data[0].b; - result[i][3] = data[0].a; - } - } else { - int currentCursor = 0; - ColorBandData currentData = data[0]; - ColorBandData nextData = data[0]; - switch (ipoType) { - case ColorBand.IPO_LINEAR: - float rDiff = 0, - gDiff = 0, - bDiff = 0, - aDiff = 0, - posDiff; - for (int i = 0; i < result.length; ++i) { - posDiff = i - currentData.pos; - result[i][0] = currentData.r + rDiff * posDiff; - result[i][1] = currentData.g + gDiff * posDiff; - result[i][2] = currentData.b + bDiff * posDiff; - result[i][3] = currentData.a + aDiff * posDiff; - if (nextData.pos == i) { - currentData = data[currentCursor++]; - if (currentCursor < data.length) { - nextData = data[currentCursor]; - // calculate differences - int d = nextData.pos - currentData.pos; - rDiff = (nextData.r - currentData.r) / d; - gDiff = (nextData.g - currentData.g) / d; - bDiff = (nextData.b - currentData.b) / d; - aDiff = (nextData.a - currentData.a) / d; - } else { - rDiff = gDiff = bDiff = aDiff = 0; - } - } - } - break; - case ColorBand.IPO_BSPLINE: - case ColorBand.IPO_CARDINAL: - Map cbDataMap = new TreeMap(); - for (int i = 0; i < data.length; ++i) { - cbDataMap.put(Integer.valueOf(i), data[i]); - } + if (data[0].pos == 0) { + cbDataMap.put(Integer.valueOf(-1), data[0]); + } else { + ColorBandData cbData = data[0].clone(); + cbData.pos = 0; + cbDataMap.put(Integer.valueOf(-1), cbData); + cbDataMap.put(Integer.valueOf(-2), cbData); + } - if (data[0].pos == 0) { - cbDataMap.put(Integer.valueOf(-1), data[0]); - } else { - ColorBandData cbData = data[0].clone(); - cbData.pos = 0; - cbDataMap.put(Integer.valueOf(-1), cbData); - cbDataMap.put(Integer.valueOf(-2), cbData); - } + if (data[data.length - 1].pos == 1000) { + cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]); + } else { + ColorBandData cbData = data[data.length - 1].clone(); + cbData.pos = 1000; + cbDataMap.put(Integer.valueOf(data.length), cbData); + cbDataMap.put(Integer.valueOf(data.length + 1), cbData); + } - if (data[data.length - 1].pos == 1000) { - cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]); - } else { - ColorBandData cbData = data[data.length - 1].clone(); - cbData.pos = 1000; - cbDataMap.put(Integer.valueOf(data.length), cbData); - cbDataMap.put(Integer.valueOf(data.length + 1), cbData); - } + float[] ipoFactors = new float[4]; + float f; - float[] ipoFactors = new float[4]; - float f; + ColorBandData data0 = this.getColorbandData(currentCursor - 2, cbDataMap); + ColorBandData data1 = this.getColorbandData(currentCursor - 1, cbDataMap); + ColorBandData data2 = this.getColorbandData(currentCursor, cbDataMap); + ColorBandData data3 = this.getColorbandData(currentCursor + 1, cbDataMap); - ColorBandData data0 = this.getColorbandData(currentCursor - 2, cbDataMap); - ColorBandData data1 = this.getColorbandData(currentCursor - 1, cbDataMap); - ColorBandData data2 = this.getColorbandData(currentCursor, cbDataMap); - ColorBandData data3 = this.getColorbandData(currentCursor + 1, cbDataMap); + for (int i = 0; i < result.length; ++i) { + if (data2.pos != data1.pos) { + f = (i - data2.pos) / (float) (data1.pos - data2.pos); + f = FastMath.clamp(f, 0.0f, 1.0f); + } else { + f = 0.0f; + } + this.getIpoData(f, ipoFactors); + result[i][0] = ipoFactors[3] * data0.r + ipoFactors[2] * data1.r + ipoFactors[1] * data2.r + ipoFactors[0] * data3.r; + result[i][1] = ipoFactors[3] * data0.g + ipoFactors[2] * data1.g + ipoFactors[1] * data2.g + ipoFactors[0] * data3.g; + result[i][2] = ipoFactors[3] * data0.b + ipoFactors[2] * data1.b + ipoFactors[1] * data2.b + ipoFactors[0] * data3.b; + result[i][3] = ipoFactors[3] * data0.a + ipoFactors[2] * data1.a + ipoFactors[1] * data2.a + ipoFactors[0] * data3.a; + result[i][0] = FastMath.clamp(result[i][0], 0.0f, 1.0f); + result[i][1] = FastMath.clamp(result[i][1], 0.0f, 1.0f); + result[i][2] = FastMath.clamp(result[i][2], 0.0f, 1.0f); + result[i][3] = FastMath.clamp(result[i][3], 0.0f, 1.0f); - for (int i = 0; i < result.length; ++i) { - if (data2.pos != data1.pos) { - f = (i - data2.pos) / (float) (data1.pos - data2.pos); - f = FastMath.clamp(f, 0.0f, 1.0f); - } else { - f = 0.0f; - } - this.getIpoData(f, ipoFactors); - result[i][0] = ipoFactors[3] * data0.r + ipoFactors[2] * data1.r + ipoFactors[1] * data2.r + ipoFactors[0] * data3.r; - result[i][1] = ipoFactors[3] * data0.g + ipoFactors[2] * data1.g + ipoFactors[1] * data2.g + ipoFactors[0] * data3.g; - result[i][2] = ipoFactors[3] * data0.b + ipoFactors[2] * data1.b + ipoFactors[1] * data2.b + ipoFactors[0] * data3.b; - result[i][3] = ipoFactors[3] * data0.a + ipoFactors[2] * data1.a + ipoFactors[1] * data2.a + ipoFactors[0] * data3.a; - result[i][0] = FastMath.clamp(result[i][0], 0.0f, 1.0f); - result[i][1] = FastMath.clamp(result[i][1], 0.0f, 1.0f); - result[i][2] = FastMath.clamp(result[i][2], 0.0f, 1.0f); - result[i][3] = FastMath.clamp(result[i][3], 0.0f, 1.0f); + if (nextData.pos == i) { + ++currentCursor; + data0 = cbDataMap.get(currentCursor - 2); + data1 = cbDataMap.get(currentCursor - 1); + data2 = cbDataMap.get(currentCursor); + data3 = cbDataMap.get(currentCursor + 1); + } + } + break; + case ColorBand.IPO_EASE: + float d, + a, + b, + d2; + for (int i = 0; i < result.length; ++i) { + if (nextData.pos != currentData.pos) { + d = (i - currentData.pos) / (float) (nextData.pos - currentData.pos); + d2 = d * d; + a = 3.0f * d2 - 2.0f * d * d2; + b = 1.0f - a; + } else { + d = a = 0.0f; + b = 1.0f; + } - if (nextData.pos == i) { - ++currentCursor; - data0 = cbDataMap.get(currentCursor - 2); - data1 = cbDataMap.get(currentCursor - 1); - data2 = cbDataMap.get(currentCursor); - data3 = cbDataMap.get(currentCursor + 1); - } - } - break; - case ColorBand.IPO_EASE: - float d, - a, - b, - d2; - for (int i = 0; i < result.length; ++i) { - if (nextData.pos != currentData.pos) { - d = (i - currentData.pos) / (float) (nextData.pos - currentData.pos); - d2 = d * d; - a = 3.0f * d2 - 2.0f * d * d2; - b = 1.0f - a; - } else { - d = a = 0.0f; - b = 1.0f; - } + result[i][0] = b * currentData.r + a * nextData.r; + result[i][1] = b * currentData.g + a * nextData.g; + result[i][2] = b * currentData.b + a * nextData.b; + result[i][3] = b * currentData.a + a * nextData.a; + if (nextData.pos == i) { + currentData = data[currentCursor++]; + if (currentCursor < data.length) { + nextData = data[currentCursor]; + } + } + } + break; + case ColorBand.IPO_CONSTANT: + for (int i = 0; i < result.length; ++i) { + result[i][0] = currentData.r; + result[i][1] = currentData.g; + result[i][2] = currentData.b; + result[i][3] = currentData.a; + if (nextData.pos == i) { + currentData = data[currentCursor++]; + if (currentCursor < data.length) { + nextData = data[currentCursor]; + } + } + } + break; + default: + throw new IllegalStateException("Unknown interpolation type: " + ipoType); + } + } + } + return result; + } - result[i][0] = b * currentData.r + a * nextData.r; - result[i][1] = b * currentData.g + a * nextData.g; - result[i][2] = b * currentData.b + a * nextData.b; - result[i][3] = b * currentData.a + a * nextData.a; - if (nextData.pos == i) { - currentData = data[currentCursor++]; - if (currentCursor < data.length) { - nextData = data[currentCursor]; - } - } - } - break; - case ColorBand.IPO_CONSTANT: - for (int i = 0; i < result.length; ++i) { - result[i][0] = currentData.r; - result[i][1] = currentData.g; - result[i][2] = currentData.b; - result[i][3] = currentData.a; - if (nextData.pos == i) { - currentData = data[currentCursor++]; - if (currentCursor < data.length) { - nextData = data[currentCursor]; - } - } - } - break; - default: - throw new IllegalStateException("Unknown interpolation type: " + ipoType); - } - } - } - return result; - } - - private ColorBandData getColorbandData(int index, Map cbDataMap) { - ColorBandData result = cbDataMap.get(index); - if(result == null) { - result = new ColorBandData(); - } - return result; - } + private ColorBandData getColorbandData(int index, Map cbDataMap) { + ColorBandData result = cbDataMap.get(index); + if (result == null) { + result = new ColorBandData(); + } + return result; + } - /** - * This method returns the data for either B-spline of Cardinal - * interpolation. - * - * @param d - * distance factor for the current intensity - * @param ipoFactors - * table to store the results (size of the table must be at least - * 4) - */ - private void getIpoData(float d, float[] ipoFactors) { - float d2 = d * d; - float d3 = d2 * d; - if (ipoType == ColorBand.IPO_BSPLINE) { - ipoFactors[0] = -0.71f * d3 + 1.42f * d2 - 0.71f * d; - ipoFactors[1] = 1.29f * d3 - 2.29f * d2 + 1.0f; - ipoFactors[2] = -1.29f * d3 + 1.58f * d2 + 0.71f * d; - ipoFactors[3] = 0.71f * d3 - 0.71f * d2; - } else if (ipoType == ColorBand.IPO_CARDINAL) { - ipoFactors[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f; - ipoFactors[1] = 0.5f * d3 - d2 + 0.6666666f; - ipoFactors[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f; - ipoFactors[3] = 0.16666666f * d3; - } else { - throw new IllegalStateException("Cannot get interpolation data for other colorband types than B-spline and Cardinal!"); - } - } + /** + * This method returns the data for either B-spline of Cardinal + * interpolation. + * + * @param d + * distance factor for the current intensity + * @param ipoFactors + * table to store the results (size of the table must be at least + * 4) + */ + private void getIpoData(float d, float[] ipoFactors) { + float d2 = d * d; + float d3 = d2 * d; + if (ipoType == ColorBand.IPO_BSPLINE) { + ipoFactors[0] = -0.71f * d3 + 1.42f * d2 - 0.71f * d; + ipoFactors[1] = 1.29f * d3 - 2.29f * d2 + 1.0f; + ipoFactors[2] = -1.29f * d3 + 1.58f * d2 + 0.71f * d; + ipoFactors[3] = 0.71f * d3 - 0.71f * d2; + } else if (ipoType == ColorBand.IPO_CARDINAL) { + ipoFactors[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f; + ipoFactors[1] = 0.5f * d3 - d2 + 0.6666666f; + ipoFactors[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f; + ipoFactors[3] = 0.16666666f * d3; + } else { + throw new IllegalStateException("Cannot get interpolation data for other colorband types than B-spline and Cardinal!"); + } + } - /** - * Class to store the single colorband cursor data. - * - * @author Marcin Roguski (Kaelthas) - */ - private static class ColorBandData implements Cloneable { - public final float r, g, b, a; - public int pos; + /** + * Class to store the single colorband cursor data. + * + * @author Marcin Roguski (Kaelthas) + */ + private static class ColorBandData implements Cloneable { + public final float r, g, b, a; + public int pos; - public ColorBandData() { - r = g = b = 0; - a = 1; - } - - /** - * Copy constructor. - */ - private ColorBandData(ColorBandData data) { - this.r = data.r; - this.g = data.g; - this.b = data.b; - this.a = data.a; - this.pos = data.pos; - } + public ColorBandData() { + r = g = b = 0; + a = 1; + } - /** - * Constructor. Loads the data from the given structure. - * - * @param cbdataStructure - * the structure containing the CBData object - */ - public ColorBandData(Structure cbdataStructure) { - this.r = ((Number) cbdataStructure.getFieldValue("r")).floatValue(); - this.g = ((Number) cbdataStructure.getFieldValue("g")).floatValue(); - this.b = ((Number) cbdataStructure.getFieldValue("b")).floatValue(); - this.a = ((Number) cbdataStructure.getFieldValue("a")).floatValue(); - this.pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f); - } + /** + * Copy constructor. + */ + private ColorBandData(ColorBandData data) { + this.r = data.r; + this.g = data.g; + this.b = data.b; + this.a = data.a; + this.pos = data.pos; + } + + /** + * Constructor. Loads the data from the given structure. + * + * @param cbdataStructure + * the structure containing the CBData object + */ + public ColorBandData(Structure cbdataStructure) { + this.r = ((Number) cbdataStructure.getFieldValue("r")).floatValue(); + this.g = ((Number) cbdataStructure.getFieldValue("g")).floatValue(); + this.b = ((Number) cbdataStructure.getFieldValue("b")).floatValue(); + this.a = ((Number) cbdataStructure.getFieldValue("a")).floatValue(); + this.pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f); + } - @Override - public ColorBandData clone() { - try { - return (ColorBandData) super.clone(); - } catch (CloneNotSupportedException e) { - return new ColorBandData(this); - } - } + @Override + public ColorBandData clone() { + try { + return (ColorBandData) super.clone(); + } catch (CloneNotSupportedException e) { + return new ColorBandData(this); + } + } - @Override - public String toString() { - return "P: " + this.pos + " [" + this.r + ", " + this.g + ", " + this.b + ", " + this.a + "]"; - } - } + @Override + public String toString() { + return "P: " + this.pos + " [" + this.r + ", " + this.g + ", " + this.b + ", " + this.a + "]"; + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java index 0412126e0..7694285c0 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java @@ -40,512 +40,511 @@ import com.jme3.texture.Texture2D; * @author Marcin Roguski (Kaelthas) */ public class CombinedTexture { - private static final Logger LOGGER = Logger.getLogger(CombinedTexture.class.getName()); - - /** The mapping type of the texture. Defined bu MaterialContext.MTEX_COL, MTEX_NOR etc. */ - private final int mappingType; - /** The data for each of the textures. */ - private List textureDatas = new ArrayList(); - /** The variable indicates if the texture was already triangulated or not. */ - private boolean wasTriangulated; - /** The result texture. */ - private Texture resultTexture; - /** The UV values for the result texture. */ - private List resultUVS; - - /** - * Constructor. Stores the texture mapping type (ie. color map, normal map). - * - * @param mappingType - * texture mapping type - */ - public CombinedTexture(int mappingType) { - this.mappingType = mappingType; - } - - /** - * This method adds a texture data to the resulting texture. - * - * @param texture - * the source texture - * @param textureBlender - * the texture blender (to mix the texture with its material - * color) - * @param uvCoordinatesType - * the type of UV coordinates - * @param projectionType - * the type of UV coordinates projection (for flat textures) - * @param textureStructure - * the texture sructure - * @param blenderContext - * the blender context - */ - public void add(Texture texture, TextureBlender textureBlender, int uvCoordinatesType, int projectionType, Structure textureStructure, BlenderContext blenderContext) { - if (!(texture instanceof GeneratedTexture) && !(texture instanceof Texture2D)) { - throw new IllegalArgumentException("Unsupported texture type: " + (texture == null ? "null" : texture.getClass())); - } - if(!(texture instanceof GeneratedTexture) || blenderContext.getBlenderKey().isLoadGeneratedTextures()) { - if(UVCoordinatesGenerator.isTextureCoordinateTypeSupported(UVCoordinatesType.valueOf(uvCoordinatesType))) { - TextureData textureData = new TextureData(); - textureData.texture = texture; - textureData.textureBlender = textureBlender; - textureData.uvCoordinatesType = UVCoordinatesType.valueOf(uvCoordinatesType); - textureData.projectionType = UVProjectionType.valueOf(projectionType); - textureData.textureStructure = textureStructure; - - if (textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) { - textureDatas.clear();// clear previous textures, they will be covered anyway - } - textureDatas.add(textureData); - } else { - LOGGER.warning("The texture coordinates type is not supported: " + UVCoordinatesType.valueOf(uvCoordinatesType) + ". The texture '" + textureStructure.getName() + "'."); - } - } - } - - /** - * This method flattens the texture and creates a single result of Texture2D - * type. - * - * @param geometry - * the geometry the texture is created for - * @param geometriesOMA - * the old memory address of the geometries list that the given - * geometry belongs to (needed for bounding box creation) - * @param userDefinedUVCoordinates - * the UV's defined by user (null or zero length table if none - * were defined) - * @param blenderContext - * the blender context - */ - @SuppressWarnings("unchecked") - public void flatten(Geometry geometry, Long geometriesOMA, List userDefinedUVCoordinates, BlenderContext blenderContext) { - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - Mesh mesh = geometry.getMesh(); - Texture previousTexture = null; - UVCoordinatesType masterUVCoordinatesType = null; - for (TextureData textureData : textureDatas) { - // decompress compressed textures (all will be merged into one texture anyway) - if (textureDatas.size() > 1 && textureData.texture.getImage().getFormat().isCompressed()) { - textureData.texture.setImage(textureHelper.decompress(textureData.texture.getImage())); - textureData.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender); - } - - if (previousTexture == null) {// the first texture will lead the others to its shape - if (textureData.texture instanceof GeneratedTexture) { - resultTexture = ((GeneratedTexture) textureData.texture).triangulate(mesh, geometriesOMA, textureData.uvCoordinatesType, blenderContext); - } else if (textureData.texture instanceof Texture2D) { - resultTexture = textureData.texture; - - if(textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { - resultUVS = userDefinedUVCoordinates; - } else { - List geometries = (List) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE); - resultUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries); - } - } - this.blend(resultTexture, textureData.textureBlender, blenderContext); - - previousTexture = resultTexture; - masterUVCoordinatesType = textureData.uvCoordinatesType; - } else { - if (textureData.texture instanceof GeneratedTexture) { - if (!(resultTexture instanceof TriangulatedTexture)) { - resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext); - resultUVS = null; - previousTexture = resultTexture; - } - - TriangulatedTexture triangulatedTexture = ((GeneratedTexture) textureData.texture).triangulate(mesh, geometriesOMA, textureData.uvCoordinatesType, blenderContext); - triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext); - triangulatedTexture.blend(textureData.textureBlender, (TriangulatedTexture) resultTexture, blenderContext); - resultTexture = previousTexture = triangulatedTexture; - } else if (textureData.texture instanceof Texture2D) { - if (masterUVCoordinatesType == textureData.uvCoordinatesType && resultTexture instanceof Texture2D) { - this.scale((Texture2D) textureData.texture, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight()); - this.merge((Texture2D) resultTexture, (Texture2D) textureData.texture); - previousTexture = resultTexture; - } else { - if (!(resultTexture instanceof TriangulatedTexture)) { - resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext); - resultUVS = null; - } - // first triangulate the current texture - List textureUVS = null; - if(textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { - textureUVS = userDefinedUVCoordinates; - } else { - List geometries = (List) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE); - textureUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries); - } - TriangulatedTexture triangulatedTexture = new TriangulatedTexture((Texture2D) textureData.texture, textureUVS, blenderContext); - // then move the texture to different UV's - triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext); - ((TriangulatedTexture) resultTexture).merge(triangulatedTexture); - } - } - } - } - - if (resultTexture instanceof TriangulatedTexture) { - if(mappingType == MaterialContext.MTEX_NOR) { - for(int i=0;i<((TriangulatedTexture) resultTexture).getFaceTextureCount();++i) { - TriangleTextureElement triangleTextureElement = ((TriangulatedTexture) resultTexture).getFaceTextureElement(i); - triangleTextureElement.image = textureHelper.convertToNormalMapTexture(triangleTextureElement.image, 1);//TODO: get proper strength factor - } - } - resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS(); - resultTexture = ((TriangulatedTexture) resultTexture).getResultTexture(); - wasTriangulated = true; - } - - // setting additional data - resultTexture.setWrap(WrapMode.Repeat); - // the filters are required if generated textures are used because - // otherwise ugly lines appear between the mesh faces - resultTexture.setMagFilter(MagFilter.Nearest); - resultTexture.setMinFilter(MinFilter.NearestNoMipMaps); - } - - /** - * This method blends the texture. - * - * @param texture - * the texture to be blended - * @param textureBlender - * blending definition for the texture - * @param blenderContext - * the blender context - */ - private void blend(Texture texture, TextureBlender textureBlender, BlenderContext blenderContext) { - if (texture instanceof TriangulatedTexture) { - ((TriangulatedTexture) texture).blend(textureBlender, null, blenderContext); - } else if (texture instanceof Texture2D) { - Image blendedImage = textureBlender.blend(texture.getImage(), null, blenderContext); - texture.setImage(blendedImage); - } else { - throw new IllegalArgumentException("Invalid type for texture to blend!"); - } - } - - /** - * This method casts the current image to the basic UV's owner UV's - * coordinates. - * - * @param basicUVSOwner - * the owner of the UV's we cast to - * @param blenderContext - * the blender context - */ - public void castToUVS(CombinedTexture basicUVSOwner, BlenderContext blenderContext) { - if (resultUVS.size() != basicUVSOwner.resultUVS.size()) { - throw new IllegalStateException("The amount of UV coordinates must be equal in order to cast one UV's onto another!"); - } - if (!resultUVS.equals(basicUVSOwner.resultUVS)) { - if (!basicUVSOwner.wasTriangulated) { - throw new IllegalStateException("The given texture must be triangulated!"); - } - if (!this.wasTriangulated) { - resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext); - resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS(); - resultTexture = ((TriangulatedTexture) resultTexture).getResultTexture(); - } - // casting algorithm - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - ImageLoader imageLoader = new ImageLoader(); - List faceTextures = new ArrayList(); - List basicUVS = basicUVSOwner.getResultUVS(); - int[] imageRectangle = new int[4];// minX, minY, maxX, maxY - int[] sourceSize = new int[2], targetSize = new int[2];// width, - // height - Vector2f[] destinationUVS = new Vector2f[3]; - Vector2f[] sourceUVS = new Vector2f[3]; - List partImageUVS = Arrays.asList(new Vector2f(), new Vector2f(), new Vector2f()); - int faceIndex = 0; - - for (int i = 0; i < basicUVS.size(); i += 3) { - // destination size nad UVS - destinationUVS[0] = basicUVS.get(i); - destinationUVS[1] = basicUVS.get(i + 1); - destinationUVS[2] = basicUVS.get(i + 2); - this.computeImageRectangle(destinationUVS, imageRectangle, basicUVSOwner.resultTexture.getImage().getWidth(), basicUVSOwner.resultTexture.getImage().getHeight(), blenderContext); - targetSize[0] = imageRectangle[2] - imageRectangle[0]; - targetSize[1] = imageRectangle[3] - imageRectangle[1]; - for (int j = 0; j < 3; ++j) { - partImageUVS.get(j).set((basicUVSOwner.resultTexture.getImage().getWidth() * destinationUVS[j].x - imageRectangle[0]) / targetSize[0], - (basicUVSOwner.resultTexture.getImage().getHeight() * destinationUVS[j].y - imageRectangle[1]) / targetSize[1]); - } - - // source size and UVS (translate UVS to (0,0) and stretch it to - // the borders of the image) - sourceUVS[0] = resultUVS.get(i); - sourceUVS[1] = resultUVS.get(i + 1); - sourceUVS[2] = resultUVS.get(i + 2); - this.computeImageRectangle(sourceUVS, imageRectangle, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight(), blenderContext); - sourceSize[0] = imageRectangle[2] - imageRectangle[0]; - sourceSize[1] = imageRectangle[3] - imageRectangle[1]; - float xTranslateFactor = imageRectangle[0] / (float) resultTexture.getImage().getWidth(); - float xStreachFactor = resultTexture.getImage().getWidth() / (float) sourceSize[0]; - float yTranslateFactor = imageRectangle[1] / (float) resultTexture.getImage().getHeight(); - float yStreachFactor = resultTexture.getImage().getHeight() / (float) sourceSize[1]; - for (int j = 0; j < 3; ++j) { - sourceUVS[j].x = (sourceUVS[j].x - xTranslateFactor) * xStreachFactor; - sourceUVS[j].y = (sourceUVS[j].y - yTranslateFactor) * yStreachFactor; - } - - AffineTransform affineTransform = textureHelper.createAffineTransform(sourceUVS, partImageUVS.toArray(new Vector2f[3]), sourceSize, targetSize); - - Image image = textureHelper.getSubimage(resultTexture.getImage(), imageRectangle[0], imageRectangle[1], imageRectangle[2], imageRectangle[3]); - - // compute the result texture - BufferedImage sourceImage = ImageToAwt.convert(image, false, true, 0); - - BufferedImage targetImage = new BufferedImage(targetSize[0], targetSize[1], sourceImage.getType()); - Graphics2D g = targetImage.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g.drawImage(sourceImage, affineTransform, null); - g.dispose(); - - Image output = imageLoader.load(targetImage, false); - faceTextures.add(new TriangleTextureElement(faceIndex++, output, partImageUVS, false, blenderContext)); - } - TriangulatedTexture triangulatedTexture = new TriangulatedTexture(faceTextures, blenderContext); - triangulatedTexture.setKeepIdenticalTextures(false); - resultTexture = triangulatedTexture.getResultTexture(); - resultUVS = basicUVS; - } - } - - /** - * This method computes the rectangle of an image constrained by the - * triangle UV coordinates. - * - * @param triangleVertices - * the triangle UV coordinates - * @param result - * the array where the result is stored - * @param totalImageWidth - * the total image width - * @param totalImageHeight - * the total image height - * @param blenderContext - * the blender context - */ - private void computeImageRectangle(Vector2f[] triangleVertices, int[] result, int totalImageWidth, int totalImageHeight, BlenderContext blenderContext) { - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - - float minX = Math.min(triangleVertices[0].x, triangleVertices[1].x); - minX = Math.min(minX, triangleVertices[2].x); - - float maxX = Math.max(triangleVertices[0].x, triangleVertices[1].x); - maxX = Math.max(maxX, triangleVertices[2].x); - - float minY = Math.min(triangleVertices[0].y, triangleVertices[1].y); - minY = Math.min(minY, triangleVertices[2].y); - - float maxY = Math.max(triangleVertices[0].y, triangleVertices[1].y); - maxY = Math.max(maxY, triangleVertices[2].y); - - result[0] = textureHelper.getPixelPosition(minX, totalImageWidth); - result[1] = textureHelper.getPixelPosition(minY, totalImageHeight); - result[2] = textureHelper.getPixelPosition(maxX, totalImageWidth); - result[3] = textureHelper.getPixelPosition(maxY, totalImageHeight); - } - - /** - * @return the result texture - */ - public Texture getResultTexture() { - return resultTexture; - } - - /** - * @return the result UV coordinates - */ - public List getResultUVS() { - return resultUVS; - } - - /** - * @return the amount of added textures - */ - public int getTexturesCount() { - return textureDatas.size(); - } - - /** - * @return true if the texture has at least one generated texture component and false otherwise - */ - public boolean hasGeneratedTextures() { - if(textureDatas != null) { - for(TextureData textureData : textureDatas) { - if(textureData.texture instanceof GeneratedTexture) { - return true; - } - } - } - return false; - } - - /** - * This method merges two given textures. The result is stored in the - * 'target' texture. - * - * @param target - * the target texture - * @param source - * the source texture - */ - private void merge(Texture2D target, Texture2D source) { - if(target.getImage().getDepth() != source.getImage().getDepth()) { - throw new IllegalArgumentException("Cannot merge images with different depths!"); - } - Image sourceImage = source.getImage(); - Image targetImage = target.getImage(); - PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); - PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); - TexturePixel sourcePixel = new TexturePixel(); - TexturePixel targetPixel = new TexturePixel(); - int depth = target.getImage().getDepth() == 0 ? 1 : target.getImage().getDepth(); - - for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { - for (int x = 0; x < sourceImage.getWidth(); ++x) { - for (int y = 0; y < sourceImage.getHeight(); ++y) { - sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y); - targetIO.read(targetImage, layerIndex, targetPixel, x, y); - targetPixel.merge(sourcePixel); - targetIO.write(targetImage, layerIndex, targetPixel, x, y); - } - } - } - } - - /** - * This method determines if the given texture has no alpha channel. - * - * @param texture - * the texture to check for alpha channel - * @return true if the texture has no alpha channel and false - * otherwise - */ - private boolean isWithoutAlpha(TextureData textureData, BlenderContext blenderContext) { - ColorBand colorBand = new ColorBand(textureData.textureStructure, blenderContext); - if (!colorBand.hasTransparencies()) { - int type = ((Number) textureData.textureStructure.getFieldValue("type")).intValue(); - if (type == TextureHelper.TEX_MAGIC) { - return true; - } - if (type == TextureHelper.TEX_VORONOI) { - int voronoiColorType = ((Number) textureData.textureStructure.getFieldValue("vn_coltype")).intValue(); - return voronoiColorType != 0;// voronoiColorType == 0: - // intensity, voronoiColorType - // != 0: col1, col2 or col3 - } - if (type == TextureHelper.TEX_CLOUDS) { - int sType = ((Number) textureData.textureStructure.getFieldValue("stype")).intValue(); - return sType == 1;// sType==0: without colors, sType==1: with - // colors - } - - // checking the flat textures for alpha values presence - if (type == TextureHelper.TEX_IMAGE) { - Image image = textureData.texture.getImage(); - switch (image.getFormat()) { - case BGR8: - case DXT1: - case Luminance16: - case Luminance16F: - case Luminance32F: - case Luminance8: - case RGB10: - case RGB111110F: - case RGB16: - case RGB16F: - case RGB32F: - case RGB565: - case RGB8: - return true;// these types have no alpha by definition - case ABGR8: - case DXT3: - case DXT5: - case Luminance16Alpha16: - case Luminance16FAlpha16F: - case Luminance8Alpha8: - case RGBA16: - case RGBA16F: - case RGBA32F: - case RGBA8:// with these types it is better to make sure if the texture is or is not transparent - PixelInputOutput pixelInputOutput = PixelIOFactory.getPixelIO(image.getFormat()); - TexturePixel pixel = new TexturePixel(); - int depth = image.getDepth() == 0 ? 1 : image.getDepth(); - for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { - for (int x = 0; x < image.getWidth(); ++x) { - for (int y = 0; y < image.getHeight(); ++y) { - pixelInputOutput.read(image, layerIndex, pixel, x, y); - if (pixel.alpha < 1.0f) { - return false; - } - } - } - } - return true; - } - } - } - return false; - } - - /** - * This method scales the given texture to the given size. - * - * @param texture - * the texture to be scaled - * @param width - * new width of the texture - * @param height - * new height of the texture - */ - private void scale(Texture2D texture, int width, int height) { - // first determine if scaling is required - boolean scaleRequired = texture.getImage().getWidth() != width || texture.getImage().getHeight() != height; - - if (scaleRequired) { - Image image = texture.getImage(); - BufferedImage sourceImage = ImageToAwt.convert(image, false, true, 0); - - int sourceWidth = sourceImage.getWidth(); - int sourceHeight = sourceImage.getHeight(); - - BufferedImage targetImage = new BufferedImage(width, height, sourceImage.getType()); - - Graphics2D g = targetImage.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g.drawImage(sourceImage, 0, 0, width, height, 0, 0, sourceWidth, sourceHeight, null); - g.dispose(); - - Image output = new ImageLoader().load(targetImage, false); - image.setWidth(width); - image.setHeight(height); - image.setData(output.getData(0)); - image.setFormat(output.getFormat()); - } - } - - /** - * A simple class to aggregate the texture data (improves code quality). - * - * @author Marcin Roguski (Kaelthas) - */ - private static class TextureData { - /** The texture. */ - public Texture texture; - /** The texture blender (to mix the texture with its material color). */ - public TextureBlender textureBlender; - /** The type of UV coordinates. */ - public UVCoordinatesType uvCoordinatesType; - /** The type of UV coordinates projection (for flat textures). */ - public UVProjectionType projectionType; - /** The texture sructure. */ - public Structure textureStructure; - } + private static final Logger LOGGER = Logger.getLogger(CombinedTexture.class.getName()); + + /** The mapping type of the texture. Defined bu MaterialContext.MTEX_COL, MTEX_NOR etc. */ + private final int mappingType; + /** The data for each of the textures. */ + private List textureDatas = new ArrayList(); + /** The variable indicates if the texture was already triangulated or not. */ + private boolean wasTriangulated; + /** The result texture. */ + private Texture resultTexture; + /** The UV values for the result texture. */ + private List resultUVS; + + /** + * Constructor. Stores the texture mapping type (ie. color map, normal map). + * + * @param mappingType + * texture mapping type + */ + public CombinedTexture(int mappingType) { + this.mappingType = mappingType; + } + + /** + * This method adds a texture data to the resulting texture. + * + * @param texture + * the source texture + * @param textureBlender + * the texture blender (to mix the texture with its material + * color) + * @param uvCoordinatesType + * the type of UV coordinates + * @param projectionType + * the type of UV coordinates projection (for flat textures) + * @param textureStructure + * the texture sructure + * @param blenderContext + * the blender context + */ + public void add(Texture texture, TextureBlender textureBlender, int uvCoordinatesType, int projectionType, Structure textureStructure, BlenderContext blenderContext) { + if (!(texture instanceof GeneratedTexture) && !(texture instanceof Texture2D)) { + throw new IllegalArgumentException("Unsupported texture type: " + (texture == null ? "null" : texture.getClass())); + } + if (!(texture instanceof GeneratedTexture) || blenderContext.getBlenderKey().isLoadGeneratedTextures()) { + if (UVCoordinatesGenerator.isTextureCoordinateTypeSupported(UVCoordinatesType.valueOf(uvCoordinatesType))) { + TextureData textureData = new TextureData(); + textureData.texture = texture; + textureData.textureBlender = textureBlender; + textureData.uvCoordinatesType = UVCoordinatesType.valueOf(uvCoordinatesType); + textureData.projectionType = UVProjectionType.valueOf(projectionType); + textureData.textureStructure = textureStructure; + + if (textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) { + textureDatas.clear();// clear previous textures, they will be covered anyway + } + textureDatas.add(textureData); + } else { + LOGGER.warning("The texture coordinates type is not supported: " + UVCoordinatesType.valueOf(uvCoordinatesType) + ". The texture '" + textureStructure.getName() + "'."); + } + } + } + + /** + * This method flattens the texture and creates a single result of Texture2D + * type. + * + * @param geometry + * the geometry the texture is created for + * @param geometriesOMA + * the old memory address of the geometries list that the given + * geometry belongs to (needed for bounding box creation) + * @param userDefinedUVCoordinates + * the UV's defined by user (null or zero length table if none + * were defined) + * @param blenderContext + * the blender context + */ + @SuppressWarnings("unchecked") + public void flatten(Geometry geometry, Long geometriesOMA, List userDefinedUVCoordinates, BlenderContext blenderContext) { + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + Mesh mesh = geometry.getMesh(); + Texture previousTexture = null; + UVCoordinatesType masterUVCoordinatesType = null; + for (TextureData textureData : textureDatas) { + // decompress compressed textures (all will be merged into one texture anyway) + if (textureDatas.size() > 1 && textureData.texture.getImage().getFormat().isCompressed()) { + textureData.texture.setImage(textureHelper.decompress(textureData.texture.getImage())); + textureData.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender); + } + + if (previousTexture == null) {// the first texture will lead the others to its shape + if (textureData.texture instanceof GeneratedTexture) { + resultTexture = ((GeneratedTexture) textureData.texture).triangulate(mesh, geometriesOMA, textureData.uvCoordinatesType, blenderContext); + } else if (textureData.texture instanceof Texture2D) { + resultTexture = textureData.texture; + + if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { + resultUVS = userDefinedUVCoordinates; + } else { + List geometries = (List) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE); + resultUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries); + } + } + this.blend(resultTexture, textureData.textureBlender, blenderContext); + + previousTexture = resultTexture; + masterUVCoordinatesType = textureData.uvCoordinatesType; + } else { + if (textureData.texture instanceof GeneratedTexture) { + if (!(resultTexture instanceof TriangulatedTexture)) { + resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext); + resultUVS = null; + previousTexture = resultTexture; + } + + TriangulatedTexture triangulatedTexture = ((GeneratedTexture) textureData.texture).triangulate(mesh, geometriesOMA, textureData.uvCoordinatesType, blenderContext); + triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext); + triangulatedTexture.blend(textureData.textureBlender, (TriangulatedTexture) resultTexture, blenderContext); + resultTexture = previousTexture = triangulatedTexture; + } else if (textureData.texture instanceof Texture2D) { + if (masterUVCoordinatesType == textureData.uvCoordinatesType && resultTexture instanceof Texture2D) { + this.scale((Texture2D) textureData.texture, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight()); + this.merge((Texture2D) resultTexture, (Texture2D) textureData.texture); + previousTexture = resultTexture; + } else { + if (!(resultTexture instanceof TriangulatedTexture)) { + resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext); + resultUVS = null; + } + // first triangulate the current texture + List textureUVS = null; + if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { + textureUVS = userDefinedUVCoordinates; + } else { + List geometries = (List) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE); + textureUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries); + } + TriangulatedTexture triangulatedTexture = new TriangulatedTexture((Texture2D) textureData.texture, textureUVS, blenderContext); + // then move the texture to different UV's + triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext); + ((TriangulatedTexture) resultTexture).merge(triangulatedTexture); + } + } + } + } + + if (resultTexture instanceof TriangulatedTexture) { + if (mappingType == MaterialContext.MTEX_NOR) { + for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) { + TriangleTextureElement triangleTextureElement = ((TriangulatedTexture) resultTexture).getFaceTextureElement(i); + triangleTextureElement.image = textureHelper.convertToNormalMapTexture(triangleTextureElement.image, 1);// TODO: get proper strength factor + } + } + resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS(); + resultTexture = ((TriangulatedTexture) resultTexture).getResultTexture(); + wasTriangulated = true; + } + + // setting additional data + resultTexture.setWrap(WrapMode.Repeat); + // the filters are required if generated textures are used because + // otherwise ugly lines appear between the mesh faces + resultTexture.setMagFilter(MagFilter.Nearest); + resultTexture.setMinFilter(MinFilter.NearestNoMipMaps); + } + + /** + * This method blends the texture. + * + * @param texture + * the texture to be blended + * @param textureBlender + * blending definition for the texture + * @param blenderContext + * the blender context + */ + private void blend(Texture texture, TextureBlender textureBlender, BlenderContext blenderContext) { + if (texture instanceof TriangulatedTexture) { + ((TriangulatedTexture) texture).blend(textureBlender, null, blenderContext); + } else if (texture instanceof Texture2D) { + Image blendedImage = textureBlender.blend(texture.getImage(), null, blenderContext); + texture.setImage(blendedImage); + } else { + throw new IllegalArgumentException("Invalid type for texture to blend!"); + } + } + + /** + * This method casts the current image to the basic UV's owner UV's + * coordinates. + * + * @param basicUVSOwner + * the owner of the UV's we cast to + * @param blenderContext + * the blender context + */ + public void castToUVS(CombinedTexture basicUVSOwner, BlenderContext blenderContext) { + if (resultUVS.size() != basicUVSOwner.resultUVS.size()) { + throw new IllegalStateException("The amount of UV coordinates must be equal in order to cast one UV's onto another!"); + } + if (!resultUVS.equals(basicUVSOwner.resultUVS)) { + if (!basicUVSOwner.wasTriangulated) { + throw new IllegalStateException("The given texture must be triangulated!"); + } + if (!this.wasTriangulated) { + resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext); + resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS(); + resultTexture = ((TriangulatedTexture) resultTexture).getResultTexture(); + } + // casting algorithm + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + ImageLoader imageLoader = new ImageLoader(); + List faceTextures = new ArrayList(); + List basicUVS = basicUVSOwner.getResultUVS(); + int[] imageRectangle = new int[4];// minX, minY, maxX, maxY + int[] sourceSize = new int[2], targetSize = new int[2];// width, + // height + Vector2f[] destinationUVS = new Vector2f[3]; + Vector2f[] sourceUVS = new Vector2f[3]; + List partImageUVS = Arrays.asList(new Vector2f(), new Vector2f(), new Vector2f()); + int faceIndex = 0; + + for (int i = 0; i < basicUVS.size(); i += 3) { + // destination size nad UVS + destinationUVS[0] = basicUVS.get(i); + destinationUVS[1] = basicUVS.get(i + 1); + destinationUVS[2] = basicUVS.get(i + 2); + this.computeImageRectangle(destinationUVS, imageRectangle, basicUVSOwner.resultTexture.getImage().getWidth(), basicUVSOwner.resultTexture.getImage().getHeight(), blenderContext); + targetSize[0] = imageRectangle[2] - imageRectangle[0]; + targetSize[1] = imageRectangle[3] - imageRectangle[1]; + for (int j = 0; j < 3; ++j) { + partImageUVS.get(j).set((basicUVSOwner.resultTexture.getImage().getWidth() * destinationUVS[j].x - imageRectangle[0]) / targetSize[0], (basicUVSOwner.resultTexture.getImage().getHeight() * destinationUVS[j].y - imageRectangle[1]) / targetSize[1]); + } + + // source size and UVS (translate UVS to (0,0) and stretch it to + // the borders of the image) + sourceUVS[0] = resultUVS.get(i); + sourceUVS[1] = resultUVS.get(i + 1); + sourceUVS[2] = resultUVS.get(i + 2); + this.computeImageRectangle(sourceUVS, imageRectangle, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight(), blenderContext); + sourceSize[0] = imageRectangle[2] - imageRectangle[0]; + sourceSize[1] = imageRectangle[3] - imageRectangle[1]; + float xTranslateFactor = imageRectangle[0] / (float) resultTexture.getImage().getWidth(); + float xStreachFactor = resultTexture.getImage().getWidth() / (float) sourceSize[0]; + float yTranslateFactor = imageRectangle[1] / (float) resultTexture.getImage().getHeight(); + float yStreachFactor = resultTexture.getImage().getHeight() / (float) sourceSize[1]; + for (int j = 0; j < 3; ++j) { + sourceUVS[j].x = (sourceUVS[j].x - xTranslateFactor) * xStreachFactor; + sourceUVS[j].y = (sourceUVS[j].y - yTranslateFactor) * yStreachFactor; + } + + AffineTransform affineTransform = textureHelper.createAffineTransform(sourceUVS, partImageUVS.toArray(new Vector2f[3]), sourceSize, targetSize); + + Image image = textureHelper.getSubimage(resultTexture.getImage(), imageRectangle[0], imageRectangle[1], imageRectangle[2], imageRectangle[3]); + + // compute the result texture + BufferedImage sourceImage = ImageToAwt.convert(image, false, true, 0); + + BufferedImage targetImage = new BufferedImage(targetSize[0], targetSize[1], sourceImage.getType()); + Graphics2D g = targetImage.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.drawImage(sourceImage, affineTransform, null); + g.dispose(); + + Image output = imageLoader.load(targetImage, false); + faceTextures.add(new TriangleTextureElement(faceIndex++, output, partImageUVS, false, blenderContext)); + } + TriangulatedTexture triangulatedTexture = new TriangulatedTexture(faceTextures, blenderContext); + triangulatedTexture.setKeepIdenticalTextures(false); + resultTexture = triangulatedTexture.getResultTexture(); + resultUVS = basicUVS; + } + } + + /** + * This method computes the rectangle of an image constrained by the + * triangle UV coordinates. + * + * @param triangleVertices + * the triangle UV coordinates + * @param result + * the array where the result is stored + * @param totalImageWidth + * the total image width + * @param totalImageHeight + * the total image height + * @param blenderContext + * the blender context + */ + private void computeImageRectangle(Vector2f[] triangleVertices, int[] result, int totalImageWidth, int totalImageHeight, BlenderContext blenderContext) { + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + + float minX = Math.min(triangleVertices[0].x, triangleVertices[1].x); + minX = Math.min(minX, triangleVertices[2].x); + + float maxX = Math.max(triangleVertices[0].x, triangleVertices[1].x); + maxX = Math.max(maxX, triangleVertices[2].x); + + float minY = Math.min(triangleVertices[0].y, triangleVertices[1].y); + minY = Math.min(minY, triangleVertices[2].y); + + float maxY = Math.max(triangleVertices[0].y, triangleVertices[1].y); + maxY = Math.max(maxY, triangleVertices[2].y); + + result[0] = textureHelper.getPixelPosition(minX, totalImageWidth); + result[1] = textureHelper.getPixelPosition(minY, totalImageHeight); + result[2] = textureHelper.getPixelPosition(maxX, totalImageWidth); + result[3] = textureHelper.getPixelPosition(maxY, totalImageHeight); + } + + /** + * @return the result texture + */ + public Texture getResultTexture() { + return resultTexture; + } + + /** + * @return the result UV coordinates + */ + public List getResultUVS() { + return resultUVS; + } + + /** + * @return the amount of added textures + */ + public int getTexturesCount() { + return textureDatas.size(); + } + + /** + * @return true if the texture has at least one generated texture component and false otherwise + */ + public boolean hasGeneratedTextures() { + if (textureDatas != null) { + for (TextureData textureData : textureDatas) { + if (textureData.texture instanceof GeneratedTexture) { + return true; + } + } + } + return false; + } + + /** + * This method merges two given textures. The result is stored in the + * 'target' texture. + * + * @param target + * the target texture + * @param source + * the source texture + */ + private void merge(Texture2D target, Texture2D source) { + if (target.getImage().getDepth() != source.getImage().getDepth()) { + throw new IllegalArgumentException("Cannot merge images with different depths!"); + } + Image sourceImage = source.getImage(); + Image targetImage = target.getImage(); + PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); + PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); + TexturePixel sourcePixel = new TexturePixel(); + TexturePixel targetPixel = new TexturePixel(); + int depth = target.getImage().getDepth() == 0 ? 1 : target.getImage().getDepth(); + + for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { + for (int x = 0; x < sourceImage.getWidth(); ++x) { + for (int y = 0; y < sourceImage.getHeight(); ++y) { + sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y); + targetIO.read(targetImage, layerIndex, targetPixel, x, y); + targetPixel.merge(sourcePixel); + targetIO.write(targetImage, layerIndex, targetPixel, x, y); + } + } + } + } + + /** + * This method determines if the given texture has no alpha channel. + * + * @param texture + * the texture to check for alpha channel + * @return true if the texture has no alpha channel and false + * otherwise + */ + private boolean isWithoutAlpha(TextureData textureData, BlenderContext blenderContext) { + ColorBand colorBand = new ColorBand(textureData.textureStructure, blenderContext); + if (!colorBand.hasTransparencies()) { + int type = ((Number) textureData.textureStructure.getFieldValue("type")).intValue(); + if (type == TextureHelper.TEX_MAGIC) { + return true; + } + if (type == TextureHelper.TEX_VORONOI) { + int voronoiColorType = ((Number) textureData.textureStructure.getFieldValue("vn_coltype")).intValue(); + return voronoiColorType != 0;// voronoiColorType == 0: + // intensity, voronoiColorType + // != 0: col1, col2 or col3 + } + if (type == TextureHelper.TEX_CLOUDS) { + int sType = ((Number) textureData.textureStructure.getFieldValue("stype")).intValue(); + return sType == 1;// sType==0: without colors, sType==1: with + // colors + } + + // checking the flat textures for alpha values presence + if (type == TextureHelper.TEX_IMAGE) { + Image image = textureData.texture.getImage(); + switch (image.getFormat()) { + case BGR8: + case DXT1: + case Luminance16: + case Luminance16F: + case Luminance32F: + case Luminance8: + case RGB10: + case RGB111110F: + case RGB16: + case RGB16F: + case RGB32F: + case RGB565: + case RGB8: + return true;// these types have no alpha by definition + case ABGR8: + case DXT3: + case DXT5: + case Luminance16Alpha16: + case Luminance16FAlpha16F: + case Luminance8Alpha8: + case RGBA16: + case RGBA16F: + case RGBA32F: + case RGBA8:// with these types it is better to make sure if the texture is or is not transparent + PixelInputOutput pixelInputOutput = PixelIOFactory.getPixelIO(image.getFormat()); + TexturePixel pixel = new TexturePixel(); + int depth = image.getDepth() == 0 ? 1 : image.getDepth(); + for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { + for (int x = 0; x < image.getWidth(); ++x) { + for (int y = 0; y < image.getHeight(); ++y) { + pixelInputOutput.read(image, layerIndex, pixel, x, y); + if (pixel.alpha < 1.0f) { + return false; + } + } + } + } + return true; + } + } + } + return false; + } + + /** + * This method scales the given texture to the given size. + * + * @param texture + * the texture to be scaled + * @param width + * new width of the texture + * @param height + * new height of the texture + */ + private void scale(Texture2D texture, int width, int height) { + // first determine if scaling is required + boolean scaleRequired = texture.getImage().getWidth() != width || texture.getImage().getHeight() != height; + + if (scaleRequired) { + Image image = texture.getImage(); + BufferedImage sourceImage = ImageToAwt.convert(image, false, true, 0); + + int sourceWidth = sourceImage.getWidth(); + int sourceHeight = sourceImage.getHeight(); + + BufferedImage targetImage = new BufferedImage(width, height, sourceImage.getType()); + + Graphics2D g = targetImage.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.drawImage(sourceImage, 0, 0, width, height, 0, 0, sourceWidth, sourceHeight, null); + g.dispose(); + + Image output = new ImageLoader().load(targetImage, false); + image.setWidth(width); + image.setHeight(height); + image.setData(output.getData(0)); + image.setFormat(output.getFormat()); + } + } + + /** + * A simple class to aggregate the texture data (improves code quality). + * + * @author Marcin Roguski (Kaelthas) + */ + private static class TextureData { + /** The texture. */ + public Texture texture; + /** The texture blender (to mix the texture with its material color). */ + public TextureBlender textureBlender; + /** The type of UV coordinates. */ + public UVCoordinatesType uvCoordinatesType; + /** The type of UV coordinates projection (for flat textures). */ + public UVProjectionType projectionType; + /** The texture sructure. */ + public Structure textureStructure; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/DDSTexelData.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/DDSTexelData.java index 4e4b4247d..5537f1e0b 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/DDSTexelData.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/DDSTexelData.java @@ -9,149 +9,149 @@ import com.jme3.texture.Image.Format; * @author Marcin Roguski (Kaelthas) */ /* package */class DDSTexelData { - /** The colors of the texes. */ - private TexturePixel[][] colors; - /** The indexes of the texels. */ - private long[] indexes; - /** The alphas of the texels (might be null). */ - private float[][] alphas; - /** The indexels of texels alpha values (might be null). */ - private long[] alphaIndexes; - /** The counter of texel x column. */ - private int xCounter; - /** The counter of texel y row. */ - private int yCounter; - /** The width of the image in pixels. */ - private int widthInPixels; - /** The height of the image in pixels. */ - private int heightInPixels; - /** The total texel count. */ - private int xTexelCount; + /** The colors of the texes. */ + private TexturePixel[][] colors; + /** The indexes of the texels. */ + private long[] indexes; + /** The alphas of the texels (might be null). */ + private float[][] alphas; + /** The indexels of texels alpha values (might be null). */ + private long[] alphaIndexes; + /** The counter of texel x column. */ + private int xCounter; + /** The counter of texel y row. */ + private int yCounter; + /** The width of the image in pixels. */ + private int widthInPixels; + /** The height of the image in pixels. */ + private int heightInPixels; + /** The total texel count. */ + private int xTexelCount; - /** - * Constructor. Allocates memory for data structures. - * - * @param compressedSize - * the size of compressed image (or its mipmap) - * @param widthToHeightRatio - * width/height ratio for the image - * @param format - * the format of the image - */ - public DDSTexelData(int compressedSize, float widthToHeightRatio, Format format) { - int texelsCount = compressedSize * 8 / format.getBitsPerPixel() / 16; - this.colors = new TexturePixel[texelsCount][]; - this.indexes = new long[texelsCount]; - this.widthInPixels = (int) (0.5f * (float) Math.sqrt(this.getSizeInBytes() / widthToHeightRatio)); - this.heightInPixels = (int) (this.widthInPixels / widthToHeightRatio); - this.xTexelCount = widthInPixels >> 2; - this.yCounter = (heightInPixels >> 2) - 1;// xCounter is 0 for now - if (format == Format.DXT3 || format == Format.DXT5) { - this.alphas = new float[texelsCount][]; - this.alphaIndexes = new long[texelsCount]; - } - } + /** + * Constructor. Allocates memory for data structures. + * + * @param compressedSize + * the size of compressed image (or its mipmap) + * @param widthToHeightRatio + * width/height ratio for the image + * @param format + * the format of the image + */ + public DDSTexelData(int compressedSize, float widthToHeightRatio, Format format) { + int texelsCount = compressedSize * 8 / format.getBitsPerPixel() / 16; + this.colors = new TexturePixel[texelsCount][]; + this.indexes = new long[texelsCount]; + this.widthInPixels = (int) (0.5f * (float) Math.sqrt(this.getSizeInBytes() / widthToHeightRatio)); + this.heightInPixels = (int) (this.widthInPixels / widthToHeightRatio); + this.xTexelCount = widthInPixels >> 2; + this.yCounter = (heightInPixels >> 2) - 1;// xCounter is 0 for now + if (format == Format.DXT3 || format == Format.DXT5) { + this.alphas = new float[texelsCount][]; + this.alphaIndexes = new long[texelsCount]; + } + } - /** - * This method adds a color and indexes for a texel. - * - * @param colors - * the colors of the texel - * @param indexes - * the indexes of the texel - */ - public void add(TexturePixel[] colors, int indexes) { - this.add(colors, indexes, null, 0); - } + /** + * This method adds a color and indexes for a texel. + * + * @param colors + * the colors of the texel + * @param indexes + * the indexes of the texel + */ + public void add(TexturePixel[] colors, int indexes) { + this.add(colors, indexes, null, 0); + } - /** - * This method adds a color, color indexes and alha values (with their - * indexes) for a texel. - * - * @param colors - * the colors of the texel - * @param indexes - * the indexes of the texel - * @param alphas - * the alpha values - * @param alphaIndexes - * the indexes of the given alpha values - */ - public void add(TexturePixel[] colors, int indexes, float[] alphas, long alphaIndexes) { - int index = yCounter * xTexelCount + xCounter; - this.colors[index] = colors; - this.indexes[index] = indexes; - if (alphas != null) { - this.alphas[index] = alphas; - this.alphaIndexes[index] = alphaIndexes; - } - ++this.xCounter; - if (this.xCounter >= this.xTexelCount) { - this.xCounter = 0; - --this.yCounter; - } - } + /** + * This method adds a color, color indexes and alha values (with their + * indexes) for a texel. + * + * @param colors + * the colors of the texel + * @param indexes + * the indexes of the texel + * @param alphas + * the alpha values + * @param alphaIndexes + * the indexes of the given alpha values + */ + public void add(TexturePixel[] colors, int indexes, float[] alphas, long alphaIndexes) { + int index = yCounter * xTexelCount + xCounter; + this.colors[index] = colors; + this.indexes[index] = indexes; + if (alphas != null) { + this.alphas[index] = alphas; + this.alphaIndexes[index] = alphaIndexes; + } + ++this.xCounter; + if (this.xCounter >= this.xTexelCount) { + this.xCounter = 0; + --this.yCounter; + } + } - /** - * This method returns the values of the pixel located on the given - * coordinates on the result image. - * - * @param x - * the x coordinate of the pixel - * @param y - * the y coordinate of the pixel - * @param result - * the table where the result is stored - * @return true if the pixel was correctly read and false if - * the position was outside the image sizes - */ - public boolean getRGBA8(int x, int y, byte[] result) { - int xTexetlIndex = x % widthInPixels / 4; - int yTexelIndex = y % heightInPixels / 4; + /** + * This method returns the values of the pixel located on the given + * coordinates on the result image. + * + * @param x + * the x coordinate of the pixel + * @param y + * the y coordinate of the pixel + * @param result + * the table where the result is stored + * @return true if the pixel was correctly read and false if + * the position was outside the image sizes + */ + public boolean getRGBA8(int x, int y, byte[] result) { + int xTexetlIndex = x % widthInPixels / 4; + int yTexelIndex = y % heightInPixels / 4; - int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex; - if (texelIndex < colors.length) { - TexturePixel[] colors = this.colors[texelIndex]; + int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex; + if (texelIndex < colors.length) { + TexturePixel[] colors = this.colors[texelIndex]; - // coordinates of the pixel in the selected texel - x = x - 4 * xTexetlIndex;// pixels are arranged from left to right - y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start) + // coordinates of the pixel in the selected texel + x = x - 4 * xTexetlIndex;// pixels are arranged from left to right + y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start) - int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2); - int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0; + int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2); + int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0; - // getting the pixel - int indexMask = colors.length - 1; - int colorIndex = (int) (this.indexes[texelIndex] >> pixelIndexInTexel & indexMask); - float alpha = this.alphas != null ? this.alphas[texelIndex][(int) (this.alphaIndexes[texelIndex] >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha; - result[0] = (byte) (colors[colorIndex].red * 255.0f); - result[1] = (byte) (colors[colorIndex].green * 255.0f); - result[2] = (byte) (colors[colorIndex].blue * 255.0f); - result[3] = (byte) (alpha * 255.0f); - return true; - } - return false; - } + // getting the pixel + int indexMask = colors.length - 1; + int colorIndex = (int) (this.indexes[texelIndex] >> pixelIndexInTexel & indexMask); + float alpha = this.alphas != null ? this.alphas[texelIndex][(int) (this.alphaIndexes[texelIndex] >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha; + result[0] = (byte) (colors[colorIndex].red * 255.0f); + result[1] = (byte) (colors[colorIndex].green * 255.0f); + result[2] = (byte) (colors[colorIndex].blue * 255.0f); + result[3] = (byte) (alpha * 255.0f); + return true; + } + return false; + } - /** - * @return the size of the decompressed texel (in bytes) - */ - public int getSizeInBytes() { - // indexes.length == count of texels - return indexes.length * 16 * 4; - } + /** + * @return the size of the decompressed texel (in bytes) + */ + public int getSizeInBytes() { + // indexes.length == count of texels + return indexes.length * 16 * 4; + } - /** - * @return image (mipmap) width - */ - public int getPixelWidth() { - return widthInPixels; - } + /** + * @return image (mipmap) width + */ + public int getPixelWidth() { + return widthInPixels; + } - /** - * @return image (mipmap) height - */ - public int getPixelHeight() { - return heightInPixels; - } + /** + * @return image (mipmap) height + */ + public int getPixelHeight() { + return heightInPixels; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java index f1d970461..f36666a2f 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java @@ -25,127 +25,127 @@ import java.util.TreeSet; * @author Marcin Roguski (Kaelthas) */ /* package */class GeneratedTexture extends Texture { - // flag values - public static final int TEX_COLORBAND = 1; - public static final int TEX_FLIPBLEND = 2; - public static final int TEX_NEGALPHA = 4; - public static final int TEX_CHECKER_ODD = 8; - public static final int TEX_CHECKER_EVEN = 16; - public static final int TEX_PRV_ALPHA = 32; - public static final int TEX_PRV_NOR = 64; - public static final int TEX_REPEAT_XMIR = 128; - public static final int TEX_REPEAT_YMIR = 256; - public static final int TEX_FLAG_MASK = TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR; + // flag values + public static final int TEX_COLORBAND = 1; + public static final int TEX_FLIPBLEND = 2; + public static final int TEX_NEGALPHA = 4; + public static final int TEX_CHECKER_ODD = 8; + public static final int TEX_CHECKER_EVEN = 16; + public static final int TEX_PRV_ALPHA = 32; + public static final int TEX_PRV_NOR = 64; + public static final int TEX_REPEAT_XMIR = 128; + public static final int TEX_REPEAT_YMIR = 256; + public static final int TEX_FLAG_MASK = TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR; - /** Material-texture link structure. */ - private final Structure mTex; - /** Texture generateo for the specified texture type. */ - private final TextureGenerator textureGenerator; + /** Material-texture link structure. */ + private final Structure mTex; + /** Texture generateo for the specified texture type. */ + private final TextureGenerator textureGenerator; - /** - * Constructor. Reads the required data from the 'tex' structure. - * - * @param tex - * the texture structure - * @param mTex - * the material-texture link data structure - * @param textureGenerator - * the generator for the required texture type - * @param blenderContext - * the blender context - */ - public GeneratedTexture(Structure tex, Structure mTex, TextureGenerator textureGenerator, BlenderContext blenderContext) { - this.mTex = mTex; - this.textureGenerator = textureGenerator; - this.textureGenerator.readData(tex, blenderContext); - super.setImage(new GeneratedTextureImage(textureGenerator.getImageFormat())); - } + /** + * Constructor. Reads the required data from the 'tex' structure. + * + * @param tex + * the texture structure + * @param mTex + * the material-texture link data structure + * @param textureGenerator + * the generator for the required texture type + * @param blenderContext + * the blender context + */ + public GeneratedTexture(Structure tex, Structure mTex, TextureGenerator textureGenerator, BlenderContext blenderContext) { + this.mTex = mTex; + this.textureGenerator = textureGenerator; + this.textureGenerator.readData(tex, blenderContext); + super.setImage(new GeneratedTextureImage(textureGenerator.getImageFormat())); + } - /** - * This method computes the textyre color/intensity at the specified (u, v, - * s) position in 3D space. - * - * @param pixel - * the pixel where the result is stored - * @param u - * the U factor - * @param v - * the V factor - * @param s - * the S factor - */ - public void getPixel(TexturePixel pixel, float u, float v, float s) { - textureGenerator.getPixel(pixel, u, v, s); - } + /** + * This method computes the textyre color/intensity at the specified (u, v, + * s) position in 3D space. + * + * @param pixel + * the pixel where the result is stored + * @param u + * the U factor + * @param v + * the V factor + * @param s + * the S factor + */ + public void getPixel(TexturePixel pixel, float u, float v, float s) { + textureGenerator.getPixel(pixel, u, v, s); + } - /** - * This method triangulates the texture. In the result we get a set of small - * flat textures for each face of the given mesh. This can be later merged - * into one flat texture. - * - * @param mesh - * the mesh we create the texture for - * @param geometriesOMA - * the old memory address of the geometries group that the given - * mesh belongs to (required for bounding box calculations) - * @param coordinatesType - * the types of UV coordinates - * @param blenderContext - * the blender context - * @return triangulated texture - */ - @SuppressWarnings("unchecked") - public TriangulatedTexture triangulate(Mesh mesh, Long geometriesOMA, UVCoordinatesType coordinatesType, BlenderContext blenderContext) { - List geometries = (List) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE); + /** + * This method triangulates the texture. In the result we get a set of small + * flat textures for each face of the given mesh. This can be later merged + * into one flat texture. + * + * @param mesh + * the mesh we create the texture for + * @param geometriesOMA + * the old memory address of the geometries group that the given + * mesh belongs to (required for bounding box calculations) + * @param coordinatesType + * the types of UV coordinates + * @param blenderContext + * the blender context + * @return triangulated texture + */ + @SuppressWarnings("unchecked") + public TriangulatedTexture triangulate(Mesh mesh, Long geometriesOMA, UVCoordinatesType coordinatesType, BlenderContext blenderContext) { + List geometries = (List) blenderContext.getLoadedFeature(geometriesOMA, LoadedFeatureDataType.LOADED_FEATURE); - int[] coordinatesSwappingIndexes = new int[] { ((Number) mTex.getFieldValue("projx")).intValue(), ((Number) mTex.getFieldValue("projy")).intValue(), ((Number) mTex.getFieldValue("projz")).intValue() }; - List uvs = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mesh, coordinatesType, coordinatesSwappingIndexes, geometries); - Vector3f[] uvsArray = uvs.toArray(new Vector3f[uvs.size()]); - BoundingBox boundingBox = UVCoordinatesGenerator.getBoundingBox(geometries); - Set triangleTextureElements = new TreeSet(new Comparator() { - public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { - return o1.faceIndex - o2.faceIndex; - } - }); - int[] indices = new int[3]; - for (int i = 0; i < mesh.getTriangleCount(); ++i) { - mesh.getTriangle(i, indices); - triangleTextureElements.add(new TriangleTextureElement(i, boundingBox, this, uvsArray, indices, blenderContext)); - } - return new TriangulatedTexture(triangleTextureElements, blenderContext); - } + int[] coordinatesSwappingIndexes = new int[] { ((Number) mTex.getFieldValue("projx")).intValue(), ((Number) mTex.getFieldValue("projy")).intValue(), ((Number) mTex.getFieldValue("projz")).intValue() }; + List uvs = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mesh, coordinatesType, coordinatesSwappingIndexes, geometries); + Vector3f[] uvsArray = uvs.toArray(new Vector3f[uvs.size()]); + BoundingBox boundingBox = UVCoordinatesGenerator.getBoundingBox(geometries); + Set triangleTextureElements = new TreeSet(new Comparator() { + public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { + return o1.faceIndex - o2.faceIndex; + } + }); + int[] indices = new int[3]; + for (int i = 0; i < mesh.getTriangleCount(); ++i) { + mesh.getTriangle(i, indices); + triangleTextureElements.add(new TriangleTextureElement(i, boundingBox, this, uvsArray, indices, blenderContext)); + } + return new TriangulatedTexture(triangleTextureElements, blenderContext); + } - @Override - public void setWrap(WrapAxis axis, WrapMode mode) { - } + @Override + public void setWrap(WrapAxis axis, WrapMode mode) { + } - @Override - public void setWrap(WrapMode mode) { - } + @Override + public void setWrap(WrapMode mode) { + } - @Override - public WrapMode getWrap(WrapAxis axis) { - return null; - } + @Override + public WrapMode getWrap(WrapAxis axis) { + return null; + } - @Override - public Type getType() { - return Type.ThreeDimensional; - } + @Override + public Type getType() { + return Type.ThreeDimensional; + } - @Override - public Texture createSimpleClone() { - return null; - } + @Override + public Texture createSimpleClone() { + return null; + } - /** - * Private class to give the format of the 'virtual' 3D texture image. - * - * @author Marcin Roguski (Kaelthas) - */ - private static class GeneratedTextureImage extends Image { - public GeneratedTextureImage(Format imageFormat) { - super.format = imageFormat; - } - } + /** + * Private class to give the format of the 'virtual' 3D texture image. + * + * @author Marcin Roguski (Kaelthas) + */ + private static class GeneratedTextureImage extends Image { + public GeneratedTextureImage(Format imageFormat) { + super.format = imageFormat; + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageLoader.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageLoader.java index 8be0a954c..ccb21bdf9 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageLoader.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/ImageLoader.java @@ -45,91 +45,91 @@ import java.util.logging.Logger; * * @author Marcin Roguski (Kaelthas) */ -/*package*/ class ImageLoader extends AWTLoader { - private static final Logger LOGGER = Logger.getLogger(ImageLoader.class.getName()); +/* package */class ImageLoader extends AWTLoader { + private static final Logger LOGGER = Logger.getLogger(ImageLoader.class.getName()); - protected DDSLoader ddsLoader = new DDSLoader(); // DirectX image loader + protected DDSLoader ddsLoader = new DDSLoader(); // DirectX image loader - /** - * This method loads the image from the blender file itself. It tries each loader to load the image. - * - * @param inputStream - * blender input stream - * @param startPosition - * position in the stream where the image data starts - * @param flipY - * if the image should be flipped (does not work with DirectX image) - * @return loaded image or null if it could not be loaded - */ - public Image loadImage(BlenderInputStream inputStream, int startPosition, boolean flipY) { - // loading using AWT loader - inputStream.setPosition(startPosition); - Image result = this.loadImage(inputStream, ImageType.AWT, flipY); - // loading using TGA loader - if (result == null) { - inputStream.setPosition(startPosition); - result = this.loadImage(inputStream, ImageType.TGA, flipY); - } - // loading using DDS loader - if (result == null) { - inputStream.setPosition(startPosition); - result = this.loadImage(inputStream, ImageType.DDS, flipY); - } + /** + * This method loads the image from the blender file itself. It tries each loader to load the image. + * + * @param inputStream + * blender input stream + * @param startPosition + * position in the stream where the image data starts + * @param flipY + * if the image should be flipped (does not work with DirectX image) + * @return loaded image or null if it could not be loaded + */ + public Image loadImage(BlenderInputStream inputStream, int startPosition, boolean flipY) { + // loading using AWT loader + inputStream.setPosition(startPosition); + Image result = this.loadImage(inputStream, ImageType.AWT, flipY); + // loading using TGA loader + if (result == null) { + inputStream.setPosition(startPosition); + result = this.loadImage(inputStream, ImageType.TGA, flipY); + } + // loading using DDS loader + if (result == null) { + inputStream.setPosition(startPosition); + result = this.loadImage(inputStream, ImageType.DDS, flipY); + } - if (result == null) { - LOGGER.warning("Image could not be loaded by none of available loaders!"); - } + if (result == null) { + LOGGER.warning("Image could not be loaded by none of available loaders!"); + } - return result; - } + return result; + } - /** - * This method loads an image of a specified type from the given input stream. - * - * @param inputStream - * the input stream we read the image from - * @param imageType - * the type of the image {@link ImageType} - * @param flipY - * if the image should be flipped (does not work with DirectX image) - * @return loaded image or null if it could not be loaded - */ - public Image loadImage(InputStream inputStream, ImageType imageType, boolean flipY) { - Image result = null; - switch (imageType) { - case AWT: - try { - result = this.load(inputStream, flipY); - } catch (Exception e) { - LOGGER.warning("Unable to load image using AWT loader!"); - } - break; - case DDS: - try { - result = ddsLoader.load(inputStream); - } catch (Exception e) { - LOGGER.warning("Unable to load image using DDS loader!"); - } - break; - case TGA: - try { - result = TGALoader.load(inputStream, flipY); - } catch (Exception e) { - LOGGER.warning("Unable to load image using TGA loader!"); - } - break; - default: - throw new IllegalStateException("Unknown image type: " + imageType); - } - return result; - } - - /** - * Image types that can be loaded. AWT: png, jpg, jped or bmp TGA: tga DDS: DirectX image files - * - * @author Marcin Roguski (Kaelthas) - */ - private static enum ImageType { - AWT, TGA, DDS; - } + /** + * This method loads an image of a specified type from the given input stream. + * + * @param inputStream + * the input stream we read the image from + * @param imageType + * the type of the image {@link ImageType} + * @param flipY + * if the image should be flipped (does not work with DirectX image) + * @return loaded image or null if it could not be loaded + */ + public Image loadImage(InputStream inputStream, ImageType imageType, boolean flipY) { + Image result = null; + switch (imageType) { + case AWT: + try { + result = this.load(inputStream, flipY); + } catch (Exception e) { + LOGGER.warning("Unable to load image using AWT loader!"); + } + break; + case DDS: + try { + result = ddsLoader.load(inputStream); + } catch (Exception e) { + LOGGER.warning("Unable to load image using DDS loader!"); + } + break; + case TGA: + try { + result = TGALoader.load(inputStream, flipY); + } catch (Exception e) { + LOGGER.warning("Unable to load image using TGA loader!"); + } + break; + default: + throw new IllegalStateException("Unknown image type: " + imageType); + } + return result; + } + + /** + * Image types that can be loaded. AWT: png, jpg, jped or bmp TGA: tga DDS: DirectX image files + * + * @author Marcin Roguski (Kaelthas) + */ + private static enum ImageType { + AWT, TGA, DDS; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java index e76d5969d..83b3a6e92 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TextureHelper.java @@ -77,612 +77,610 @@ import com.jme3.util.BufferUtils; * @author Marcin Roguski */ public class TextureHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(TextureHelper.class.getName()); - - // texture types - public static final int TEX_NONE = 0; - public static final int TEX_CLOUDS = 1; - public static final int TEX_WOOD = 2; - public static final int TEX_MARBLE = 3; - public static final int TEX_MAGIC = 4; - public static final int TEX_BLEND = 5; - public static final int TEX_STUCCI = 6; - public static final int TEX_NOISE = 7; - public static final int TEX_IMAGE = 8; - public static final int TEX_PLUGIN = 9; - public static final int TEX_ENVMAP = 10; - public static final int TEX_MUSGRAVE = 11; - public static final int TEX_VORONOI = 12; - public static final int TEX_DISTNOISE = 13; - public static final int TEX_POINTDENSITY = 14; // v. - // 25+ - public static final int TEX_VOXELDATA = 15; // v. - // 25+ - - private TextureGeneratorFactory textureGeneratorFactory; - - /** - * This constructor parses the given blender version and stores the result. - * It creates noise generator and texture generators. - * - * @param blenderVersion - * the version read from the blend file - * @param fixUpAxis - * a variable that indicates if the Y asxis is the UP axis or not - */ - public TextureHelper(String blenderVersion, boolean fixUpAxis) { - super(blenderVersion, false); - textureGeneratorFactory = new TextureGeneratorFactory(blenderVersion); - } - - /** - * This class returns a texture read from the file or from packed blender - * data. The returned texture has the name set to the value of its blender - * type. - * - * @param tex - * texture structure filled with data - * @param blenderContext - * the blender context - * @return the texture that can be used by JME engine - * @throws BlenderFileException - * this exception is thrown when the blend file structure is - * somehow invalid or corrupted - */ - public Texture getTexture(Structure tex, Structure mTex, BlenderContext blenderContext) throws BlenderFileException { - Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); - if (result != null) { - return result; - } - int type = ((Number) tex.getFieldValue("type")).intValue(); - int imaflag = ((Number) tex.getFieldValue("imaflag")).intValue(); - - switch (type) { - case TEX_IMAGE:// (it is first because probably this will be most commonly used) - Pointer pImage = (Pointer) tex.getFieldValue("ima"); - if (pImage.isNotNull()) { - Structure image = pImage.fetchData(blenderContext.getInputStream()).get(0); - Texture loadedTexture = this.loadTexture(image, imaflag, blenderContext); - if(loadedTexture != null) { - result = loadedTexture; - this.applyColorbandAndColorFactors(tex, result.getImage(), blenderContext); - } - } - break; - case TEX_CLOUDS: - case TEX_WOOD: - case TEX_MARBLE: - case TEX_MAGIC: - case TEX_BLEND: - case TEX_STUCCI: - case TEX_NOISE: - case TEX_MUSGRAVE: - case TEX_VORONOI: - case TEX_DISTNOISE: - result = new GeneratedTexture(tex, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext); - break; - case TEX_NONE:// No texture, do nothing - break; - case TEX_POINTDENSITY: - case TEX_VOXELDATA: - case TEX_PLUGIN: - case TEX_ENVMAP: - LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, tex.getName() }); - break; - default: - throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + tex.getName()); - } - if (result != null) { - result.setName(tex.getName()); - result.setWrap(WrapMode.Repeat); - - //decide if the mipmaps will be generated - switch(blenderContext.getBlenderKey().getMipmapGenerationMethod()) { - case ALWAYS_GENERATE: - result.setMinFilter(MinFilter.Trilinear); - break; - case NEVER_GENERATE: - break; - case GENERATE_WHEN_NEEDED: - if((imaflag & 0x04) != 0) { - result.setMinFilter(MinFilter.Trilinear); - } - break; - default: - throw new IllegalStateException("Unknown mipmap generation method: " + - blenderContext.getBlenderKey().getMipmapGenerationMethod()); - } - - if (type != TEX_IMAGE) {// only generated textures should have this key - result.setKey(new GeneratedTextureKey(tex.getName())); - } - - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), tex.getOldMemoryAddress() }); - } - blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), tex.getName(), tex, result); - } - return result; - } - - /** - * This method converts the given texture into normal-map texture. - * - * @param source - * the source texture - * @param strengthFactor - * the normal strength factor - * @return normal-map texture - */ - public Image convertToNormalMapTexture(Image source, float strengthFactor) { - BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); - - BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); - BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); - ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); - gscale.filter(sourceImage, heightMap); - - Vector3f S = new Vector3f(); - Vector3f T = new Vector3f(); - Vector3f N = new Vector3f(); - - for (int x = 0; x < bumpMap.getWidth(); ++x) { - for (int y = 0; y < bumpMap.getHeight(); ++y) { - // generating bump pixel - S.x = 1; - S.y = 0; - S.z = strengthFactor * this.getHeight(heightMap, x + 1, y) - strengthFactor * this.getHeight(heightMap, x - 1, y); - T.x = 0; - T.y = 1; - T.z = strengthFactor * this.getHeight(heightMap, x, y + 1) - strengthFactor * this.getHeight(heightMap, x, y - 1); - - float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1); - N.x = -S.z; - N.y = -T.z; - N.z = 1; - N.divideLocal(den); - - // setting thge pixel in the result image - bumpMap.setRGB(x, y, this.vectorToColor(N.x, N.y, N.z)); - } - } - ByteBuffer byteBuffer = BufferUtils.createByteBuffer(source.getWidth() * source.getHeight() * 3); - ImageToAwt.convert(bumpMap, Format.RGB8, byteBuffer); - return new Image(Format.RGB8, source.getWidth(), source.getHeight(), byteBuffer); - } - - /** - * This method decompresses the given image. If the given image is already - * decompressed nothing happens and it is simply returned. - * - * @param image - * the image to decompress - * @return the decompressed image - */ - public Image decompress(Image image) { - Format format = image.getFormat(); - int depth = image.getDepth(); - if(depth == 0) { - depth = 1; - } - ArrayList dataArray = new ArrayList(depth); - int[] sizes = image.getMipMapSizes() != null ? image.getMipMapSizes() : new int[1]; - int[] newMipmapSizes = image.getMipMapSizes() != null ? new int[image.getMipMapSizes().length] : null; - - for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { - ByteBuffer data = image.getData(dataLayerIndex); - data.rewind(); - if(sizes.length == 1) { - sizes[0] = data.remaining(); - } - float widthToHeightRatio = image.getWidth() / image.getHeight();//this should always be constant for each mipmap - List texelDataList = new ArrayList(sizes.length); - int maxPosition = 0, resultSize = 0; - - for(int sizeIndex=0;sizeIndex col1) { - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - } else { - // creating color2 = 1/2color0 + 1/2color1 - colors[2].fromPixel(colors[0]); - colors[2].add(colors[1]); - colors[2].mult(0.5f); - - colors[3].fromARGB8(0); - } - int indexes = data.getInt();// 4-byte table with color indexes in decompressed table - texelData.add(colors, indexes); - } - break; - case DXT3:// BC2 - while (data.position() < maxPosition) { - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; - long alpha = data.getLong(); - float[] alphas = new float[16]; - long alphasIndex = 0; - for (int i = 0; i < 16; ++i) { - alphasIndex |= i << i * 4; - byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4); - alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; - } - - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - - int indexes = data.getInt();// 4-byte table with color indexes in decompressed table - texelData.add(colors, indexes, alphas, alphasIndex); - } - break; - case DXT5:// BC3 - float[] alphas = new float[8]; - while (data.position() < maxPosition) { - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; - alphas[0] = data.get() * 255.0f; - alphas[1] = data.get() * 255.0f; - long alphaIndices = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40; - if (alphas[0] > alphas[1]) {// 6 interpolated alpha values. - alphas[2] = (6 * alphas[0] + alphas[1]) / 7; - alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7; - alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7; - alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7; - alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7; - alphas[7] = (alphas[0] + 6 * alphas[1]) / 7; - } else { - alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f; - alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f; - alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f; - alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f; - alphas[6] = 0; - alphas[7] = 1; - } - - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - - int indexes = data.getInt();// 4-byte table with color indexes in decompressed table - texelData.add(colors, indexes, alphas, alphaIndices); - } - break; - default: - throw new IllegalStateException("Unknown compressed format: " + format); - } - newMipmapSizes[sizeIndex] = texelData.getSizeInBytes(); - resultSize += texelData.getSizeInBytes(); - } - byte[] bytes = new byte[resultSize]; - int offset = 0; - byte[] pixelBytes = new byte[4]; - for(DDSTexelData texelData : texelDataList) { - for (int i = 0; i < texelData.getPixelWidth(); ++i) { - for (int j = 0; j < texelData.getPixelHeight(); ++j) { - if(texelData.getRGBA8(i, j, pixelBytes)) { - bytes[offset + (j * texelData.getPixelWidth() + i) * 4] = pixelBytes[0]; - bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 1] = pixelBytes[1]; - bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 2] = pixelBytes[2]; - bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 3] = pixelBytes[3]; - } else { - break; - } - } - } - offset += texelData.getSizeInBytes(); - } - dataArray.add(BufferUtils.createByteBuffer(bytes)); - } - - Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) : - new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0)); - if(newMipmapSizes != null) { - result.setMipMapSizes(newMipmapSizes); - } - return result; - } - - /** - * This method returns the height represented by the specified pixel in the - * given texture. The given texture should be a height-map. - * - * @param image - * the height-map texture - * @param x - * pixel's X coordinate - * @param y - * pixel's Y coordinate - * @return height reprezented by the given texture in the specified location - */ - protected int getHeight(BufferedImage image, int x, int y) { - if (x < 0) { - x = 0; - } else if (x >= image.getWidth()) { - x = image.getWidth() - 1; - } - if (y < 0) { - y = 0; - } else if (y >= image.getHeight()) { - y = image.getHeight() - 1; - } - return image.getRGB(x, y) & 0xff; - } - - /** - * This method transforms given vector's coordinates into ARGB color (A is - * always = 255). - * - * @param x - * X factor of the vector - * @param y - * Y factor of the vector - * @param z - * Z factor of the vector - * @return color representation of the given vector - */ - protected int vectorToColor(float x, float y, float z) { - int r = Math.round(255 * (x + 1f) / 2f); - int g = Math.round(255 * (y + 1f) / 2f); - int b = Math.round(255 * (z + 1f) / 2f); - return (255 << 24) + (r << 16) + (g << 8) + b; - } - - /** - * This class returns a texture read from the file or from packed blender - * data. - * - * @param imageStructure - * image structure filled with data - * @param imaflag - * the image flag - * @param blenderContext - * the blender context - * @return the texture that can be used by JME engine - * @throws BlenderFileException - * this exception is thrown when the blend file structure is - * somehow invalid or corrupted - */ - protected Texture loadTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress()); - Texture result = null; - Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); - if (im == null) { - String texturePath = imageStructure.getFieldValue("name").toString(); - Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile"); - if (pPackedFile.isNull()) { - LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath); - result = this.loadImageFromFile(texturePath, imaflag, blenderContext); - } else { - LOGGER.fine("Packed texture. Reading directly from the blend file!"); - Structure packedFile = pPackedFile.fetchData(blenderContext.getInputStream()).get(0); - Pointer pData = (Pointer) packedFile.getFieldValue("data"); - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress()); - blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition()); - ImageLoader imageLoader = new ImageLoader(); - - // Should the texture be flipped? It works for sinbad .. - result = new Texture2D(imageLoader.loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true)); - } - } else { - result = new Texture2D(im); - } - return result; - } - - /** - * This method creates the affine transform that is used to transform a - * triangle defined by one UV coordinates into a triangle defined by - * different UV's. - * - * @param source - * source UV coordinates - * @param dest - * target UV coordinates - * @param sourceSize - * the width and height of the source image - * @param targetSize - * the width and height of the target image - * @return affine transform to transform one triangle to another - */ - public AffineTransform createAffineTransform(Vector2f[] source, Vector2f[] dest, int[] sourceSize, int[] targetSize) { - float x11 = source[0].getX() * sourceSize[0]; - float x12 = source[0].getY() * sourceSize[1]; - float x21 = source[1].getX() * sourceSize[0]; - float x22 = source[1].getY() * sourceSize[1]; - float x31 = source[2].getX() * sourceSize[0]; - float x32 = source[2].getY() * sourceSize[1]; - float y11 = dest[0].getX() * targetSize[0]; - float y12 = dest[0].getY() * targetSize[1]; - float y21 = dest[1].getX() * targetSize[0]; - float y22 = dest[1].getY() * targetSize[1]; - float y31 = dest[2].getX() * targetSize[0]; - float y32 = dest[2].getY() * targetSize[1]; - - float a1 = ((y11 - y21) * (x12 - x32) - (y11 - y31) * (x12 - x22)) / ((x11 - x21) * (x12 - x32) - (x11 - x31) * (x12 - x22)); - float a2 = ((y11 - y21) * (x11 - x31) - (y11 - y31) * (x11 - x21)) / ((x12 - x22) * (x11 - x31) - (x12 - x32) * (x11 - x21)); - float a3 = y11 - a1 * x11 - a2 * x12; - float a4 = ((y12 - y22) * (x12 - x32) - (y12 - y32) * (x12 - x22)) / ((x11 - x21) * (x12 - x32) - (x11 - x31) * (x12 - x22)); - float a5 = ((y12 - y22) * (x11 - x31) - (y12 - y32) * (x11 - x21)) / ((x12 - x22) * (x11 - x31) - (x12 - x32) * (x11 - x21)); - float a6 = y12 - a4 * x11 - a5 * x12; - return new AffineTransform(a1, a4, a2, a5, a3, a6); - } - - /** - * This method returns the proper pixel position on the image. - * - * @param pos - * the relative position (value of range <0, 1> (both inclusive)) - * @param size - * the size of the line the pixel lies on (width, heigth or - * depth) - * @return the integer index of the pixel on the line of the specified width - */ - public int getPixelPosition(float pos, int size) { - float pixelWidth = 1 / (float) size; - pos *= size; - int result = (int) pos; - // here is where we repair floating point operations errors :) - if (Math.abs(result - pos) > pixelWidth) { - ++result; - } - return result; - } - - /** - * This method returns subimage of the give image. The subimage is - * constrained by the rectangle coordinates. The source image is unchanged. - * - * @param image - * the image to be subimaged - * @param minX - * minimum X position - * @param minY - * minimum Y position - * @param maxX - * maximum X position - * @param maxY - * maximum Y position - * @return a part of the given image - */ - public Image getSubimage(Image image, int minX, int minY, int maxX, int maxY) { - if (minY > maxY) { - throw new IllegalArgumentException("Minimum Y value is higher than maximum Y value!"); - } - if (minX > maxX) { - throw new IllegalArgumentException("Minimum Y value is higher than maximum Y value!"); - } - if (image.getData().size() > 1) { - throw new IllegalArgumentException("Only flat images are allowed for subimage operation!"); - } - if (image.getMipMapSizes() != null) { - LOGGER.warning("Subimaging image with mipmaps is not yet supported!"); - } - - int width = maxX - minX; - int height = maxY - minY; - ByteBuffer data = BufferUtils.createByteBuffer(width * height * (image.getFormat().getBitsPerPixel() >> 3)); - - Image result = new Image(image.getFormat(), width, height, data); - PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); - TexturePixel pixel = new TexturePixel(); - - for (int x = minX; x < maxX; ++x) { - for (int y = minY; y < maxY; ++y) { - pixelIO.read(image, 0, pixel, x, y); - pixelIO.write(result, 0, pixel, x - minX, y - minY); - } - } - return result; - } - - /** - * This method applies the colorband and color factors to image type - * textures. If there is no colorband defined for the texture or the color - * factors are all equal to 1.0f then no changes are made. - * - * @param tex - * the texture structure - * @param image - * the image that will be altered if necessary - * @param blenderContext - * the blender context - */ - private void applyColorbandAndColorFactors(Structure tex, Image image, BlenderContext blenderContext) { - float rfac = ((Number) tex.getFieldValue("rfac")).floatValue(); - float gfac = ((Number) tex.getFieldValue("gfac")).floatValue(); - float bfac = ((Number) tex.getFieldValue("bfac")).floatValue(); - float[][] colorBand = new ColorBand(tex, blenderContext).computeValues(); - int depth = image.getDepth() == 0 ? 1 : image.getDepth(); - - if (colorBand != null) { - TexturePixel pixel = new TexturePixel(); - PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat()); - for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { - for (int x = 0; x < image.getWidth(); ++x) { - for (int y = 0; y < image.getHeight(); ++y) { - imageIO.read(image, layerIndex, pixel, x, y); - - int colorbandIndex = (int) (pixel.alpha * 1000.0f); - pixel.red = colorBand[colorbandIndex][0] * rfac; - pixel.green = colorBand[colorbandIndex][1] * gfac; - pixel.blue = colorBand[colorbandIndex][2] * bfac; - pixel.alpha = colorBand[colorbandIndex][3]; - - imageIO.write(image, layerIndex, pixel, x, y); - } - } - } - } else if (rfac != 1.0f || gfac != 1.0f || bfac != 1.0f) { - TexturePixel pixel = new TexturePixel(); - PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat()); - for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { - for (int x = 0; x < image.getWidth(); ++x) { - for (int y = 0; y < image.getHeight(); ++y) { - imageIO.read(image, layerIndex, pixel, x, y); - - pixel.red *= rfac; - pixel.green *= gfac; - pixel.blue *= bfac; - - imageIO.write(image, layerIndex, pixel, x, y); - } - } - } - } - } + private static final Logger LOGGER = Logger.getLogger(TextureHelper.class.getName()); + + // texture types + public static final int TEX_NONE = 0; + public static final int TEX_CLOUDS = 1; + public static final int TEX_WOOD = 2; + public static final int TEX_MARBLE = 3; + public static final int TEX_MAGIC = 4; + public static final int TEX_BLEND = 5; + public static final int TEX_STUCCI = 6; + public static final int TEX_NOISE = 7; + public static final int TEX_IMAGE = 8; + public static final int TEX_PLUGIN = 9; + public static final int TEX_ENVMAP = 10; + public static final int TEX_MUSGRAVE = 11; + public static final int TEX_VORONOI = 12; + public static final int TEX_DISTNOISE = 13; + public static final int TEX_POINTDENSITY = 14; // v. + // 25+ + public static final int TEX_VOXELDATA = 15; // v. + // 25+ + + private TextureGeneratorFactory textureGeneratorFactory; + + /** + * This constructor parses the given blender version and stores the result. + * It creates noise generator and texture generators. + * + * @param blenderVersion + * the version read from the blend file + * @param fixUpAxis + * a variable that indicates if the Y asxis is the UP axis or not + */ + public TextureHelper(String blenderVersion, boolean fixUpAxis) { + super(blenderVersion, false); + textureGeneratorFactory = new TextureGeneratorFactory(blenderVersion); + } + + /** + * This class returns a texture read from the file or from packed blender + * data. The returned texture has the name set to the value of its blender + * type. + * + * @param tex + * texture structure filled with data + * @param blenderContext + * the blender context + * @return the texture that can be used by JME engine + * @throws BlenderFileException + * this exception is thrown when the blend file structure is + * somehow invalid or corrupted + */ + public Texture getTexture(Structure tex, Structure mTex, BlenderContext blenderContext) throws BlenderFileException { + Texture result = (Texture) blenderContext.getLoadedFeature(tex.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (result != null) { + return result; + } + int type = ((Number) tex.getFieldValue("type")).intValue(); + int imaflag = ((Number) tex.getFieldValue("imaflag")).intValue(); + + switch (type) { + case TEX_IMAGE:// (it is first because probably this will be most commonly used) + Pointer pImage = (Pointer) tex.getFieldValue("ima"); + if (pImage.isNotNull()) { + Structure image = pImage.fetchData(blenderContext.getInputStream()).get(0); + Texture loadedTexture = this.loadTexture(image, imaflag, blenderContext); + if (loadedTexture != null) { + result = loadedTexture; + this.applyColorbandAndColorFactors(tex, result.getImage(), blenderContext); + } + } + break; + case TEX_CLOUDS: + case TEX_WOOD: + case TEX_MARBLE: + case TEX_MAGIC: + case TEX_BLEND: + case TEX_STUCCI: + case TEX_NOISE: + case TEX_MUSGRAVE: + case TEX_VORONOI: + case TEX_DISTNOISE: + result = new GeneratedTexture(tex, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext); + break; + case TEX_NONE:// No texture, do nothing + break; + case TEX_POINTDENSITY: + case TEX_VOXELDATA: + case TEX_PLUGIN: + case TEX_ENVMAP: + LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, tex.getName() }); + break; + default: + throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + tex.getName()); + } + if (result != null) { + result.setName(tex.getName()); + result.setWrap(WrapMode.Repeat); + + // decide if the mipmaps will be generated + switch (blenderContext.getBlenderKey().getMipmapGenerationMethod()) { + case ALWAYS_GENERATE: + result.setMinFilter(MinFilter.Trilinear); + break; + case NEVER_GENERATE: + break; + case GENERATE_WHEN_NEEDED: + if ((imaflag & 0x04) != 0) { + result.setMinFilter(MinFilter.Trilinear); + } + break; + default: + throw new IllegalStateException("Unknown mipmap generation method: " + blenderContext.getBlenderKey().getMipmapGenerationMethod()); + } + + if (type != TEX_IMAGE) {// only generated textures should have this key + result.setKey(new GeneratedTextureKey(tex.getName())); + } + + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), tex.getOldMemoryAddress() }); + } + blenderContext.addLoadedFeatures(tex.getOldMemoryAddress(), tex.getName(), tex, result); + } + return result; + } + + /** + * This method converts the given texture into normal-map texture. + * + * @param source + * the source texture + * @param strengthFactor + * the normal strength factor + * @return normal-map texture + */ + public Image convertToNormalMapTexture(Image source, float strengthFactor) { + BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); + + BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); + BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); + ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); + gscale.filter(sourceImage, heightMap); + + Vector3f S = new Vector3f(); + Vector3f T = new Vector3f(); + Vector3f N = new Vector3f(); + + for (int x = 0; x < bumpMap.getWidth(); ++x) { + for (int y = 0; y < bumpMap.getHeight(); ++y) { + // generating bump pixel + S.x = 1; + S.y = 0; + S.z = strengthFactor * this.getHeight(heightMap, x + 1, y) - strengthFactor * this.getHeight(heightMap, x - 1, y); + T.x = 0; + T.y = 1; + T.z = strengthFactor * this.getHeight(heightMap, x, y + 1) - strengthFactor * this.getHeight(heightMap, x, y - 1); + + float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1); + N.x = -S.z; + N.y = -T.z; + N.z = 1; + N.divideLocal(den); + + // setting thge pixel in the result image + bumpMap.setRGB(x, y, this.vectorToColor(N.x, N.y, N.z)); + } + } + ByteBuffer byteBuffer = BufferUtils.createByteBuffer(source.getWidth() * source.getHeight() * 3); + ImageToAwt.convert(bumpMap, Format.RGB8, byteBuffer); + return new Image(Format.RGB8, source.getWidth(), source.getHeight(), byteBuffer); + } + + /** + * This method decompresses the given image. If the given image is already + * decompressed nothing happens and it is simply returned. + * + * @param image + * the image to decompress + * @return the decompressed image + */ + public Image decompress(Image image) { + Format format = image.getFormat(); + int depth = image.getDepth(); + if (depth == 0) { + depth = 1; + } + ArrayList dataArray = new ArrayList(depth); + int[] sizes = image.getMipMapSizes() != null ? image.getMipMapSizes() : new int[1]; + int[] newMipmapSizes = image.getMipMapSizes() != null ? new int[image.getMipMapSizes().length] : null; + + for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { + ByteBuffer data = image.getData(dataLayerIndex); + data.rewind(); + if (sizes.length == 1) { + sizes[0] = data.remaining(); + } + float widthToHeightRatio = image.getWidth() / image.getHeight();// this should always be constant for each mipmap + List texelDataList = new ArrayList(sizes.length); + int maxPosition = 0, resultSize = 0; + + for (int sizeIndex = 0; sizeIndex < sizes.length; ++sizeIndex) { + maxPosition += sizes[sizeIndex]; + DDSTexelData texelData = new DDSTexelData(sizes[sizeIndex], widthToHeightRatio, format); + texelDataList.add(texelData); + switch (format) { + case DXT1:// BC1 + case DXT1A: + while (data.position() < maxPosition) { + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + if (col0 > col1) { + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + } else { + // creating color2 = 1/2color0 + 1/2color1 + colors[2].fromPixel(colors[0]); + colors[2].add(colors[1]); + colors[2].mult(0.5f); + + colors[3].fromARGB8(0); + } + int indexes = data.getInt();// 4-byte table with color indexes in decompressed table + texelData.add(colors, indexes); + } + break; + case DXT3:// BC2 + while (data.position() < maxPosition) { + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; + long alpha = data.getLong(); + float[] alphas = new float[16]; + long alphasIndex = 0; + for (int i = 0; i < 16; ++i) { + alphasIndex |= i << i * 4; + byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4); + alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; + } + + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + + int indexes = data.getInt();// 4-byte table with color indexes in decompressed table + texelData.add(colors, indexes, alphas, alphasIndex); + } + break; + case DXT5:// BC3 + float[] alphas = new float[8]; + while (data.position() < maxPosition) { + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; + alphas[0] = data.get() * 255.0f; + alphas[1] = data.get() * 255.0f; + long alphaIndices = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40; + if (alphas[0] > alphas[1]) {// 6 interpolated alpha values. + alphas[2] = (6 * alphas[0] + alphas[1]) / 7; + alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7; + alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7; + alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7; + alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7; + alphas[7] = (alphas[0] + 6 * alphas[1]) / 7; + } else { + alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f; + alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f; + alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f; + alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f; + alphas[6] = 0; + alphas[7] = 1; + } + + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + + int indexes = data.getInt();// 4-byte table with color indexes in decompressed table + texelData.add(colors, indexes, alphas, alphaIndices); + } + break; + default: + throw new IllegalStateException("Unknown compressed format: " + format); + } + newMipmapSizes[sizeIndex] = texelData.getSizeInBytes(); + resultSize += texelData.getSizeInBytes(); + } + byte[] bytes = new byte[resultSize]; + int offset = 0; + byte[] pixelBytes = new byte[4]; + for (DDSTexelData texelData : texelDataList) { + for (int i = 0; i < texelData.getPixelWidth(); ++i) { + for (int j = 0; j < texelData.getPixelHeight(); ++j) { + if (texelData.getRGBA8(i, j, pixelBytes)) { + bytes[offset + (j * texelData.getPixelWidth() + i) * 4] = pixelBytes[0]; + bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 1] = pixelBytes[1]; + bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 2] = pixelBytes[2]; + bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 3] = pixelBytes[3]; + } else { + break; + } + } + } + offset += texelData.getSizeInBytes(); + } + dataArray.add(BufferUtils.createByteBuffer(bytes)); + } + + Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray) : new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0)); + if (newMipmapSizes != null) { + result.setMipMapSizes(newMipmapSizes); + } + return result; + } + + /** + * This method returns the height represented by the specified pixel in the + * given texture. The given texture should be a height-map. + * + * @param image + * the height-map texture + * @param x + * pixel's X coordinate + * @param y + * pixel's Y coordinate + * @return height reprezented by the given texture in the specified location + */ + protected int getHeight(BufferedImage image, int x, int y) { + if (x < 0) { + x = 0; + } else if (x >= image.getWidth()) { + x = image.getWidth() - 1; + } + if (y < 0) { + y = 0; + } else if (y >= image.getHeight()) { + y = image.getHeight() - 1; + } + return image.getRGB(x, y) & 0xff; + } + + /** + * This method transforms given vector's coordinates into ARGB color (A is + * always = 255). + * + * @param x + * X factor of the vector + * @param y + * Y factor of the vector + * @param z + * Z factor of the vector + * @return color representation of the given vector + */ + protected int vectorToColor(float x, float y, float z) { + int r = Math.round(255 * (x + 1f) / 2f); + int g = Math.round(255 * (y + 1f) / 2f); + int b = Math.round(255 * (z + 1f) / 2f); + return (255 << 24) + (r << 16) + (g << 8) + b; + } + + /** + * This class returns a texture read from the file or from packed blender + * data. + * + * @param imageStructure + * image structure filled with data + * @param imaflag + * the image flag + * @param blenderContext + * the blender context + * @return the texture that can be used by JME engine + * @throws BlenderFileException + * this exception is thrown when the blend file structure is + * somehow invalid or corrupted + */ + protected Texture loadTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException { + LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress()); + Texture result = null; + Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE); + if (im == null) { + String texturePath = imageStructure.getFieldValue("name").toString(); + Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile"); + if (pPackedFile.isNull()) { + LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath); + result = this.loadImageFromFile(texturePath, imaflag, blenderContext); + } else { + LOGGER.fine("Packed texture. Reading directly from the blend file!"); + Structure packedFile = pPackedFile.fetchData(blenderContext.getInputStream()).get(0); + Pointer pData = (Pointer) packedFile.getFieldValue("data"); + FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress()); + blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition()); + ImageLoader imageLoader = new ImageLoader(); + + // Should the texture be flipped? It works for sinbad .. + result = new Texture2D(imageLoader.loadImage(blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true)); + } + } else { + result = new Texture2D(im); + } + return result; + } + + /** + * This method creates the affine transform that is used to transform a + * triangle defined by one UV coordinates into a triangle defined by + * different UV's. + * + * @param source + * source UV coordinates + * @param dest + * target UV coordinates + * @param sourceSize + * the width and height of the source image + * @param targetSize + * the width and height of the target image + * @return affine transform to transform one triangle to another + */ + public AffineTransform createAffineTransform(Vector2f[] source, Vector2f[] dest, int[] sourceSize, int[] targetSize) { + float x11 = source[0].getX() * sourceSize[0]; + float x12 = source[0].getY() * sourceSize[1]; + float x21 = source[1].getX() * sourceSize[0]; + float x22 = source[1].getY() * sourceSize[1]; + float x31 = source[2].getX() * sourceSize[0]; + float x32 = source[2].getY() * sourceSize[1]; + float y11 = dest[0].getX() * targetSize[0]; + float y12 = dest[0].getY() * targetSize[1]; + float y21 = dest[1].getX() * targetSize[0]; + float y22 = dest[1].getY() * targetSize[1]; + float y31 = dest[2].getX() * targetSize[0]; + float y32 = dest[2].getY() * targetSize[1]; + + float a1 = ((y11 - y21) * (x12 - x32) - (y11 - y31) * (x12 - x22)) / ((x11 - x21) * (x12 - x32) - (x11 - x31) * (x12 - x22)); + float a2 = ((y11 - y21) * (x11 - x31) - (y11 - y31) * (x11 - x21)) / ((x12 - x22) * (x11 - x31) - (x12 - x32) * (x11 - x21)); + float a3 = y11 - a1 * x11 - a2 * x12; + float a4 = ((y12 - y22) * (x12 - x32) - (y12 - y32) * (x12 - x22)) / ((x11 - x21) * (x12 - x32) - (x11 - x31) * (x12 - x22)); + float a5 = ((y12 - y22) * (x11 - x31) - (y12 - y32) * (x11 - x21)) / ((x12 - x22) * (x11 - x31) - (x12 - x32) * (x11 - x21)); + float a6 = y12 - a4 * x11 - a5 * x12; + return new AffineTransform(a1, a4, a2, a5, a3, a6); + } + + /** + * This method returns the proper pixel position on the image. + * + * @param pos + * the relative position (value of range <0, 1> (both inclusive)) + * @param size + * the size of the line the pixel lies on (width, heigth or + * depth) + * @return the integer index of the pixel on the line of the specified width + */ + public int getPixelPosition(float pos, int size) { + float pixelWidth = 1 / (float) size; + pos *= size; + int result = (int) pos; + // here is where we repair floating point operations errors :) + if (Math.abs(result - pos) > pixelWidth) { + ++result; + } + return result; + } + + /** + * This method returns subimage of the give image. The subimage is + * constrained by the rectangle coordinates. The source image is unchanged. + * + * @param image + * the image to be subimaged + * @param minX + * minimum X position + * @param minY + * minimum Y position + * @param maxX + * maximum X position + * @param maxY + * maximum Y position + * @return a part of the given image + */ + public Image getSubimage(Image image, int minX, int minY, int maxX, int maxY) { + if (minY > maxY) { + throw new IllegalArgumentException("Minimum Y value is higher than maximum Y value!"); + } + if (minX > maxX) { + throw new IllegalArgumentException("Minimum Y value is higher than maximum Y value!"); + } + if (image.getData().size() > 1) { + throw new IllegalArgumentException("Only flat images are allowed for subimage operation!"); + } + if (image.getMipMapSizes() != null) { + LOGGER.warning("Subimaging image with mipmaps is not yet supported!"); + } + + int width = maxX - minX; + int height = maxY - minY; + ByteBuffer data = BufferUtils.createByteBuffer(width * height * (image.getFormat().getBitsPerPixel() >> 3)); + + Image result = new Image(image.getFormat(), width, height, data); + PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); + TexturePixel pixel = new TexturePixel(); + + for (int x = minX; x < maxX; ++x) { + for (int y = minY; y < maxY; ++y) { + pixelIO.read(image, 0, pixel, x, y); + pixelIO.write(result, 0, pixel, x - minX, y - minY); + } + } + return result; + } + + /** + * This method applies the colorband and color factors to image type + * textures. If there is no colorband defined for the texture or the color + * factors are all equal to 1.0f then no changes are made. + * + * @param tex + * the texture structure + * @param image + * the image that will be altered if necessary + * @param blenderContext + * the blender context + */ + private void applyColorbandAndColorFactors(Structure tex, Image image, BlenderContext blenderContext) { + float rfac = ((Number) tex.getFieldValue("rfac")).floatValue(); + float gfac = ((Number) tex.getFieldValue("gfac")).floatValue(); + float bfac = ((Number) tex.getFieldValue("bfac")).floatValue(); + float[][] colorBand = new ColorBand(tex, blenderContext).computeValues(); + int depth = image.getDepth() == 0 ? 1 : image.getDepth(); + + if (colorBand != null) { + TexturePixel pixel = new TexturePixel(); + PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat()); + for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { + for (int x = 0; x < image.getWidth(); ++x) { + for (int y = 0; y < image.getHeight(); ++y) { + imageIO.read(image, layerIndex, pixel, x, y); + + int colorbandIndex = (int) (pixel.alpha * 1000.0f); + pixel.red = colorBand[colorbandIndex][0] * rfac; + pixel.green = colorBand[colorbandIndex][1] * gfac; + pixel.blue = colorBand[colorbandIndex][2] * bfac; + pixel.alpha = colorBand[colorbandIndex][3]; + + imageIO.write(image, layerIndex, pixel, x, y); + } + } + } + } else if (rfac != 1.0f || gfac != 1.0f || bfac != 1.0f) { + TexturePixel pixel = new TexturePixel(); + PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat()); + for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { + for (int x = 0; x < image.getWidth(); ++x) { + for (int y = 0; y < image.getHeight(); ++y) { + imageIO.read(image, layerIndex, pixel, x, y); + + pixel.red *= rfac; + pixel.green *= gfac; + pixel.blue *= bfac; + + imageIO.write(image, layerIndex, pixel, x, y); + } + } + } + } + } /** * This method loads the textre from outside the blend file using the @@ -695,10 +693,13 @@ public class TextureHelper extends AbstractBlenderHelper { * be found, it will issue a load attempt for the initial path anyway so the * failed load can be reported by the AssetManagers callback methods for * failed assets. - * - * @param name the path to the image - * @param imaflag the image flag - * @param blenderContext the blender context + * + * @param name + * the path to the image + * @param imaflag + * the image flag + * @param blenderContext + * the blender context * @return the loaded image or null if the image cannot be found */ protected Texture loadImageFromFile(String name, int imaflag, BlenderContext blenderContext) { @@ -710,9 +711,9 @@ public class TextureHelper extends AbstractBlenderHelper { return null; // no extension means not a valid image } - //decide if the mipmaps will be generated + // decide if the mipmaps will be generated boolean generateMipmaps = false; - switch(blenderContext.getBlenderKey().getMipmapGenerationMethod()) { + switch (blenderContext.getBlenderKey().getMipmapGenerationMethod()) { case ALWAYS_GENERATE: generateMipmaps = true; break; @@ -722,10 +723,9 @@ public class TextureHelper extends AbstractBlenderHelper { generateMipmaps = (imaflag & 0x04) != 0; break; default: - throw new IllegalStateException("Unknown mipmap generation method: " + - blenderContext.getBlenderKey().getMipmapGenerationMethod()); + throw new IllegalStateException("Unknown mipmap generation method: " + blenderContext.getBlenderKey().getMipmapGenerationMethod()); } - + AssetManager assetManager = blenderContext.getAssetManager(); name = name.replace('\\', '/'); Texture result = null; @@ -801,8 +801,8 @@ public class TextureHelper extends AbstractBlenderHelper { return result; } - @Override - public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0; - } + @Override + public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { + return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0; + } } \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java index e0598de5c..9e7c5b4ff 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TexturePixel.java @@ -9,357 +9,357 @@ import com.jme3.math.FastMath; * @author Marcin Roguski (Kaelthas) */ public class TexturePixel implements Cloneable { - /** The pixel data. */ - public float intensity, red, green, blue, alpha; - - /** - * Copies the values from the given pixel. - * - * @param pixel - * the pixel that we read from - */ - public void fromPixel(TexturePixel pixel) { - this.intensity = pixel.intensity; - this.red = pixel.red; - this.green = pixel.green; - this.blue = pixel.blue; - this.alpha = pixel.alpha; - } - - /** - * Copies the values from the given color. - * - * @param colorRGBA - * the color that we read from - */ - public void fromColor(ColorRGBA colorRGBA) { - this.red = colorRGBA.r; - this.green = colorRGBA.g; - this.blue = colorRGBA.b; - this.alpha = colorRGBA.a; - } - - /** - * Copies the values from the given values. - * - * @param a - * the alpha value - * @param r - * the red value - * @param g - * the green value - * @param b - * the blue value - */ - public void fromARGB(float a, float r, float g, float b) { - this.alpha = a; - this.red = r; - this.green = g; - this.blue = b; - } - - /** - * Copies the values from the given values. - * - * @param a - * the alpha value - * @param r - * the red value - * @param g - * the green value - * @param b - * the blue value - */ - public void fromARGB8(byte a, byte r, byte g, byte b) { - this.alpha = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; - this.red = r >= 0 ? r / 255.0f : 1.0f - ~r / 255.0f; - this.green = g >= 0 ? g / 255.0f : 1.0f - ~g / 255.0f; - this.blue = b >= 0 ? b / 255.0f : 1.0f - ~b / 255.0f; - } - - /** - * Copies the values from the given values. - * - * @param a - * the alpha value - * @param r - * the red value - * @param g - * the green value - * @param b - * the blue value - */ - public void fromARGB16(short a, short r, short g, short b) { - this.alpha = a >= 0 ? a / 65535.0f : 1.0f - ~a / 65535.0f; - this.red = r >= 0 ? r / 65535.0f : 1.0f - ~r / 65535.0f; - this.green = g >= 0 ? g / 65535.0f : 1.0f - ~g / 65535.0f; - this.blue = b >= 0 ? b / 65535.0f : 1.0f - ~b / 65535.0f; - } - - /** - * Copies the intensity from the given value. - * - * @param intensity - * the intensity value - */ - public void fromIntensity(byte intensity) { - this.intensity = intensity >= 0 ? intensity / 255.0f : 1.0f - ~intensity / 255.0f; - } - - /** - * Copies the intensity from the given value. - * - * @param intensity - * the intensity value - */ - public void fromIntensity(short intensity) { - this.intensity = intensity >= 0 ? intensity / 65535.0f : 1.0f - ~intensity / 65535.0f; - } - - /** - * This method sets the alpha value (converts it to float number from range - * <0, 1>). - * - * @param alpha - * the alpha value - */ - public void setAlpha(byte alpha) { - this.alpha = alpha >= 0 ? alpha / 255.0f : 1.0f - ~alpha / 255.0f; - } - - /** - * This method sets the alpha value (converts it to float number from range - * <0, 1>). - * - * @param alpha - * the alpha value - */ - public void setAlpha(short alpha) { - this.alpha = alpha >= 0 ? alpha / 65535.0f : 1.0f - ~alpha / 65535.0f; - } - - /** - * Copies the values from the given integer that stores the ARGB8 data. - * - * @param argb8 - * the data stored in an integer - */ - public void fromARGB8(int argb8) { - byte pixelValue = (byte) ((argb8 & 0xFF000000) >> 24); - this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - pixelValue = (byte) ((argb8 & 0xFF0000) >> 16); - this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - pixelValue = (byte) ((argb8 & 0xFF00) >> 8); - this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - pixelValue = (byte) (argb8 & 0xFF); - this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - } - - /** - * Stores RGBA values in the given array. - * - * @param result - * the array to store values - */ - public void toRGBA(float[] result) { - result[0] = this.red; - result[1] = this.green; - result[2] = this.blue; - result[3] = this.alpha; - } - - /** - * Stores the data in the given table. - * - * @param result - * the result table - */ - public void toRGBA8(byte[] result) { - result[0] = (byte) (this.red * 255.0f); - result[1] = (byte) (this.green * 255.0f); - result[2] = (byte) (this.blue * 255.0f); - result[3] = (byte) (this.alpha * 255.0f); - } - - /** - * Stores the pixel values in the integer. - * - * @return the integer that stores the pixel values - */ - public int toARGB8() { - int result = 0; - int b = (int) (this.alpha * 255.0f); - result |= b << 24; - b = (int) (this.red * 255.0f); - result |= b << 16; - b = (int) (this.green * 255.0f); - result |= b << 8; - b = (int) (this.blue * 255.0f); - result |= b; - return result; - } - - /** - * @return the intensity of the pixel - */ - public byte getInt() { - return (byte) (this.intensity * 255.0f); - } - - /** - * @return the alpha value of the pixel - */ - public byte getA8() { - return (byte) (this.alpha * 255.0f); - } - - /** - * @return the alpha red of the pixel - */ - public byte getR8() { - return (byte) (this.red * 255.0f); - } - - /** - * @return the green value of the pixel - */ - public byte getG8() { - return (byte) (this.green * 255.0f); - } - - /** - * @return the blue value of the pixel - */ - public byte getB8() { - return (byte) (this.blue * 255.0f); - } - - /** - * @return the alpha value of the pixel - */ - public short getA16() { - return (byte) (this.alpha * 65535.0f); - } - - /** - * @return the alpha red of the pixel - */ - public short getR16() { - return (byte) (this.red * 65535.0f); - } - - /** - * @return the green value of the pixel - */ - public short getG16() { - return (byte) (this.green * 65535.0f); - } - - /** - * @return the blue value of the pixel - */ - public short getB16() { - return (byte) (this.blue * 65535.0f); - } - - /** - * Merges two pixels (adds the values of each color). - * - * @param pixel - * the pixel we merge with - */ - public void merge(TexturePixel pixel) { - float oneMinusAlpha = 1 - pixel.alpha; - this.red = oneMinusAlpha * this.red + pixel.alpha * pixel.red; - this.green = oneMinusAlpha * this.green + pixel.alpha * pixel.green; - this.blue = oneMinusAlpha * this.blue + pixel.alpha * pixel.blue; - this.alpha = (this.alpha + pixel.alpha) * 0.5f; - } - - /** - * This method negates the colors. - */ - public void negate() { - this.red = 1.0f - this.red; - this.green = 1.0f - this.green; - this.blue = 1.0f - this.blue; - this.alpha = 1.0f - this.alpha; - } - - /** - * This method clears the pixel values. - */ - public void clear() { - this.intensity = this.blue = this.red = this.green = this.alpha = 0.0f; - } - - /** - * This method adds the calues of the given pixel to the current pixel. - * - * @param pixel - * the pixel we add - */ - public void add(TexturePixel pixel) { - this.red += pixel.red; - this.green += pixel.green; - this.blue += pixel.blue; - this.alpha += pixel.alpha; - this.intensity += pixel.intensity; - } - - /** - * This method multiplies the values of the given pixel by the given value. - * - * @param value - * multiplication factor - */ - public void mult(float value) { - this.red *= value; - this.green *= value; - this.blue *= value; - this.alpha *= value; - this.intensity *= value; - } - - /** - * This method divides the values of the given pixel by the given value. - * ATTENTION! Beware of the zero value. This will cause you NaN's in the - * pixel values. - * - * @param value - * division factor - */ - public void divide(float value) { - this.red /= value; - this.green /= value; - this.blue /= value; - this.alpha /= value; - this.intensity /= value; - } - - /** - * This method clamps the pixel values to the given borders. - * - * @param min - * the minimum value - * @param max - * the maximum value - */ - public void clamp(float min, float max) { - this.red = FastMath.clamp(this.red, min, max); - this.green = FastMath.clamp(this.green, min, max); - this.blue = FastMath.clamp(this.blue, min, max); - this.alpha = FastMath.clamp(this.alpha, min, max); - this.intensity = FastMath.clamp(this.intensity, min, max); - } - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - @Override - public String toString() { - return "[" + red + ", " + green + ", " + blue + ", " + alpha + " {" + intensity + "}]"; - } + /** The pixel data. */ + public float intensity, red, green, blue, alpha; + + /** + * Copies the values from the given pixel. + * + * @param pixel + * the pixel that we read from + */ + public void fromPixel(TexturePixel pixel) { + this.intensity = pixel.intensity; + this.red = pixel.red; + this.green = pixel.green; + this.blue = pixel.blue; + this.alpha = pixel.alpha; + } + + /** + * Copies the values from the given color. + * + * @param colorRGBA + * the color that we read from + */ + public void fromColor(ColorRGBA colorRGBA) { + this.red = colorRGBA.r; + this.green = colorRGBA.g; + this.blue = colorRGBA.b; + this.alpha = colorRGBA.a; + } + + /** + * Copies the values from the given values. + * + * @param a + * the alpha value + * @param r + * the red value + * @param g + * the green value + * @param b + * the blue value + */ + public void fromARGB(float a, float r, float g, float b) { + this.alpha = a; + this.red = r; + this.green = g; + this.blue = b; + } + + /** + * Copies the values from the given values. + * + * @param a + * the alpha value + * @param r + * the red value + * @param g + * the green value + * @param b + * the blue value + */ + public void fromARGB8(byte a, byte r, byte g, byte b) { + this.alpha = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; + this.red = r >= 0 ? r / 255.0f : 1.0f - ~r / 255.0f; + this.green = g >= 0 ? g / 255.0f : 1.0f - ~g / 255.0f; + this.blue = b >= 0 ? b / 255.0f : 1.0f - ~b / 255.0f; + } + + /** + * Copies the values from the given values. + * + * @param a + * the alpha value + * @param r + * the red value + * @param g + * the green value + * @param b + * the blue value + */ + public void fromARGB16(short a, short r, short g, short b) { + this.alpha = a >= 0 ? a / 65535.0f : 1.0f - ~a / 65535.0f; + this.red = r >= 0 ? r / 65535.0f : 1.0f - ~r / 65535.0f; + this.green = g >= 0 ? g / 65535.0f : 1.0f - ~g / 65535.0f; + this.blue = b >= 0 ? b / 65535.0f : 1.0f - ~b / 65535.0f; + } + + /** + * Copies the intensity from the given value. + * + * @param intensity + * the intensity value + */ + public void fromIntensity(byte intensity) { + this.intensity = intensity >= 0 ? intensity / 255.0f : 1.0f - ~intensity / 255.0f; + } + + /** + * Copies the intensity from the given value. + * + * @param intensity + * the intensity value + */ + public void fromIntensity(short intensity) { + this.intensity = intensity >= 0 ? intensity / 65535.0f : 1.0f - ~intensity / 65535.0f; + } + + /** + * This method sets the alpha value (converts it to float number from range + * <0, 1>). + * + * @param alpha + * the alpha value + */ + public void setAlpha(byte alpha) { + this.alpha = alpha >= 0 ? alpha / 255.0f : 1.0f - ~alpha / 255.0f; + } + + /** + * This method sets the alpha value (converts it to float number from range + * <0, 1>). + * + * @param alpha + * the alpha value + */ + public void setAlpha(short alpha) { + this.alpha = alpha >= 0 ? alpha / 65535.0f : 1.0f - ~alpha / 65535.0f; + } + + /** + * Copies the values from the given integer that stores the ARGB8 data. + * + * @param argb8 + * the data stored in an integer + */ + public void fromARGB8(int argb8) { + byte pixelValue = (byte) ((argb8 & 0xFF000000) >> 24); + this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + pixelValue = (byte) ((argb8 & 0xFF0000) >> 16); + this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + pixelValue = (byte) ((argb8 & 0xFF00) >> 8); + this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + pixelValue = (byte) (argb8 & 0xFF); + this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + } + + /** + * Stores RGBA values in the given array. + * + * @param result + * the array to store values + */ + public void toRGBA(float[] result) { + result[0] = this.red; + result[1] = this.green; + result[2] = this.blue; + result[3] = this.alpha; + } + + /** + * Stores the data in the given table. + * + * @param result + * the result table + */ + public void toRGBA8(byte[] result) { + result[0] = (byte) (this.red * 255.0f); + result[1] = (byte) (this.green * 255.0f); + result[2] = (byte) (this.blue * 255.0f); + result[3] = (byte) (this.alpha * 255.0f); + } + + /** + * Stores the pixel values in the integer. + * + * @return the integer that stores the pixel values + */ + public int toARGB8() { + int result = 0; + int b = (int) (this.alpha * 255.0f); + result |= b << 24; + b = (int) (this.red * 255.0f); + result |= b << 16; + b = (int) (this.green * 255.0f); + result |= b << 8; + b = (int) (this.blue * 255.0f); + result |= b; + return result; + } + + /** + * @return the intensity of the pixel + */ + public byte getInt() { + return (byte) (this.intensity * 255.0f); + } + + /** + * @return the alpha value of the pixel + */ + public byte getA8() { + return (byte) (this.alpha * 255.0f); + } + + /** + * @return the alpha red of the pixel + */ + public byte getR8() { + return (byte) (this.red * 255.0f); + } + + /** + * @return the green value of the pixel + */ + public byte getG8() { + return (byte) (this.green * 255.0f); + } + + /** + * @return the blue value of the pixel + */ + public byte getB8() { + return (byte) (this.blue * 255.0f); + } + + /** + * @return the alpha value of the pixel + */ + public short getA16() { + return (byte) (this.alpha * 65535.0f); + } + + /** + * @return the alpha red of the pixel + */ + public short getR16() { + return (byte) (this.red * 65535.0f); + } + + /** + * @return the green value of the pixel + */ + public short getG16() { + return (byte) (this.green * 65535.0f); + } + + /** + * @return the blue value of the pixel + */ + public short getB16() { + return (byte) (this.blue * 65535.0f); + } + + /** + * Merges two pixels (adds the values of each color). + * + * @param pixel + * the pixel we merge with + */ + public void merge(TexturePixel pixel) { + float oneMinusAlpha = 1 - pixel.alpha; + this.red = oneMinusAlpha * this.red + pixel.alpha * pixel.red; + this.green = oneMinusAlpha * this.green + pixel.alpha * pixel.green; + this.blue = oneMinusAlpha * this.blue + pixel.alpha * pixel.blue; + this.alpha = (this.alpha + pixel.alpha) * 0.5f; + } + + /** + * This method negates the colors. + */ + public void negate() { + this.red = 1.0f - this.red; + this.green = 1.0f - this.green; + this.blue = 1.0f - this.blue; + this.alpha = 1.0f - this.alpha; + } + + /** + * This method clears the pixel values. + */ + public void clear() { + this.intensity = this.blue = this.red = this.green = this.alpha = 0.0f; + } + + /** + * This method adds the calues of the given pixel to the current pixel. + * + * @param pixel + * the pixel we add + */ + public void add(TexturePixel pixel) { + this.red += pixel.red; + this.green += pixel.green; + this.blue += pixel.blue; + this.alpha += pixel.alpha; + this.intensity += pixel.intensity; + } + + /** + * This method multiplies the values of the given pixel by the given value. + * + * @param value + * multiplication factor + */ + public void mult(float value) { + this.red *= value; + this.green *= value; + this.blue *= value; + this.alpha *= value; + this.intensity *= value; + } + + /** + * This method divides the values of the given pixel by the given value. + * ATTENTION! Beware of the zero value. This will cause you NaN's in the + * pixel values. + * + * @param value + * division factor + */ + public void divide(float value) { + this.red /= value; + this.green /= value; + this.blue /= value; + this.alpha /= value; + this.intensity /= value; + } + + /** + * This method clamps the pixel values to the given borders. + * + * @param min + * the minimum value + * @param max + * the maximum value + */ + public void clamp(float min, float max) { + this.red = FastMath.clamp(this.red, min, max); + this.green = FastMath.clamp(this.green, min, max); + this.blue = FastMath.clamp(this.blue, min, max); + this.alpha = FastMath.clamp(this.alpha, min, max); + this.intensity = FastMath.clamp(this.intensity, min, max); + } + + @Override + public Object clone() throws CloneNotSupportedException { + return super.clone(); + } + + @Override + public String toString() { + return "[" + red + ", " + green + ", " + blue + ", " + alpha + " {" + intensity + "}]"; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java index 956e80508..4fa34248e 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java @@ -41,648 +41,646 @@ import com.jme3.util.BufferUtils; * @author Marcin Roguski (Kaelthas) */ /* package */class TriangulatedTexture extends Texture2D { - /** The result image format. */ - private Format format; - /** The collection of images for each face. */ - private Collection faceTextures; - /** - * The maximum texture size (width/height). This is taken from the blender - * key. - */ - private int maxTextureSize; - /** A variable that can prevent removing identical textures. */ - private boolean keepIdenticalTextures = false; - /** The result texture. */ - private Texture2D resultTexture; - /** The result texture's UV coordinates. */ - private List resultUVS; - - /** - * This method triangulates the given flat texture. The given texture is not - * changed. - * - * @param texture2d - * the texture to be triangulated - * @param uvs - * the UV coordinates for each face - */ - public TriangulatedTexture(Texture2D texture2d, List uvs, BlenderContext blenderContext) { - maxTextureSize = blenderContext.getBlenderKey().getMaxTextureSize(); - faceTextures = new TreeSet(new Comparator() { - public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { - return o1.faceIndex - o2.faceIndex; - } - }); - int facesCount = uvs.size() / 3; - for (int i = 0; i < facesCount; ++i) { - faceTextures.add(new TriangleTextureElement(i, texture2d.getImage(), uvs, true, blenderContext)); - } - this.format = texture2d.getImage().getFormat(); - } - - /** - * Constructor that simply stores precalculated images. - * - * @param faceTextures - * a collection of images for the mesh's faces - * @param blenderContext - * the blender context - */ - public TriangulatedTexture(Collection faceTextures, BlenderContext blenderContext) { - maxTextureSize = blenderContext.getBlenderKey().getMaxTextureSize(); - this.faceTextures = faceTextures; - for (TriangleTextureElement faceTextureElement : faceTextures) { - if (format == null) { - format = faceTextureElement.image.getFormat(); - } else if (format != faceTextureElement.image.getFormat()) { - throw new IllegalArgumentException("Face texture element images MUST have the same image format!"); - } - } - } - - /** - * This method blends the each image using the given blender and taking base - * texture into consideration. - * - * @param textureBlender - * the texture blender that holds the blending definition - * @param baseTexture - * the texture that is 'below' the current texture (can be null) - * @param blenderContext - * the blender context - */ - public void blend(TextureBlender textureBlender, TriangulatedTexture baseTexture, BlenderContext blenderContext) { - Format newFormat = null; - for (TriangleTextureElement triangleTextureElement : this.faceTextures) { - Image baseImage = baseTexture == null ? null : baseTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image; - triangleTextureElement.image = textureBlender.blend(triangleTextureElement.image, baseImage, blenderContext); - if (newFormat == null) { - newFormat = triangleTextureElement.image.getFormat(); - } else if (newFormat != triangleTextureElement.image.getFormat()) { - throw new IllegalArgumentException("Face texture element images MUST have the same image format!"); - } - } - this.format = newFormat; - } - - /** - * This method alters the images to fit them into UV coordinates of the - * given target texture. - * - * @param targetTexture - * the texture to whose UV coordinates we fit current images - * @param blenderContext - * the blender context - */ - public void castToUVS(TriangulatedTexture targetTexture, BlenderContext blenderContext) { - int[] sourceSize = new int[2], targetSize = new int[2]; - ImageLoader imageLoader = new ImageLoader(); - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - for (TriangleTextureElement entry : faceTextures) { - TriangleTextureElement targetFaceTextureElement = targetTexture.getFaceTextureElement(entry.faceIndex); - Vector2f[] dest = targetFaceTextureElement.uv; - - // get the sizes of the source and target images - sourceSize[0] = entry.image.getWidth(); - sourceSize[1] = entry.image.getHeight(); - targetSize[0] = targetFaceTextureElement.image.getWidth(); - targetSize[1] = targetFaceTextureElement.image.getHeight(); - - // create triangle transformation - AffineTransform affineTransform = textureHelper.createAffineTransform(entry.uv, dest, sourceSize, targetSize); - - // compute the result texture - BufferedImage sourceImage = ImageToAwt.convert(entry.image, false, true, 0); - - BufferedImage targetImage = new BufferedImage(targetSize[0], targetSize[1], sourceImage.getType()); - Graphics2D g = targetImage.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g.drawImage(sourceImage, affineTransform, null); - g.dispose(); - - Image output = imageLoader.load(targetImage, false); - entry.image = output; - entry.uv[0].set(dest[0]); - entry.uv[1].set(dest[1]); - entry.uv[2].set(dest[2]); - } - } - - /** - * This method merges the current texture with the given one. The given - * texture is not changed. - * - * @param triangulatedTexture - * the texture we merge current texture with - */ - public void merge(TriangulatedTexture triangulatedTexture) { - TexturePixel sourcePixel = new TexturePixel(); - TexturePixel targetPixel = new TexturePixel(); - for (TriangleTextureElement triangleTextureElement : this.faceTextures) { - Image sourceImage = triangulatedTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image; - Image targetImage = triangleTextureElement.image; - PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); - PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); - - for (int x = 0; x < sourceImage.getWidth(); ++x) { - for (int y = 0; y < sourceImage.getHeight(); ++y) { - sourceIO.read(sourceImage, 0, sourcePixel, x, y); - targetIO.read(targetImage, 0, targetPixel, x, y); - targetPixel.merge(sourcePixel); - targetIO.write(targetImage, 0, targetPixel, x, y); - } - } - } - } - - /** - * This method returns the flat texture. It is calculated if required or if - * it was not created before. Images that are identical are discarded to - * reduce the texture size. - * - * @param rebuild - * a variable that forces texture recomputation (even if it was - * computed vefore) - * @return flat result texture (all images merged into one) - */ - public Texture2D getResultTexture(boolean rebuild) { - if (resultTexture == null || rebuild) { - // sorting the parts by their height (from highest to the lowest) - List list = new ArrayList(faceTextures); - Collections.sort(list, new Comparator() { - public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { - return o2.image.getHeight() - o1.image.getHeight(); - } - }); - - // arraging the images on the resulting image (calculating the result image width and height) - Set duplicatedFaceIndexes = new HashSet(); - int resultImageHeight = list.get(0).image.getHeight(); - int resultImageWidth = 0; - int currentXPos = 0, currentYPos = 0; - Map imageLayoutData = new HashMap(list.size()); - while (list.size() > 0) { - TriangleTextureElement currentElement = list.remove(0); - if (currentXPos + currentElement.image.getWidth() > maxTextureSize) { - currentXPos = 0; - currentYPos = resultImageHeight; - resultImageHeight += currentElement.image.getHeight(); - } - Integer[] currentPositions = new Integer[] { currentXPos, currentYPos }; - imageLayoutData.put(currentElement, currentPositions); - - if(keepIdenticalTextures) {// removing identical images - for (int i = 0; i < list.size(); ++i) { - if (currentElement.image.equals(list.get(i).image)) { - duplicatedFaceIndexes.add(list.get(i).faceIndex); - imageLayoutData.put(list.remove(i--), currentPositions); - } - } - } - - currentXPos += currentElement.image.getWidth(); - resultImageWidth = Math.max(resultImageWidth, currentXPos); - // currentYPos += currentElement.image.getHeight(); - - // TODO: implement that to compact the result image - // try to add smaller images below the current one - // int remainingHeight = resultImageHeight - - // currentElement.image.getHeight(); - // while(remainingHeight > 0) { - // for(int i=list.size() - 1;i>=0;--i) { - // - // } - // } - } - - // computing the result UV coordinates - resultUVS = new ArrayList(imageLayoutData.size() * 3); - for (int i = 0; i < imageLayoutData.size() * 3; ++i) { - resultUVS.add(null); - } - Vector2f[] uvs = new Vector2f[3]; - for (Entry entry : imageLayoutData.entrySet()) { - Integer[] position = entry.getValue(); - entry.getKey().computeFinalUVCoordinates(resultImageWidth, resultImageHeight, position[0], position[1], uvs); - resultUVS.set(entry.getKey().faceIndex * 3, uvs[0]); - resultUVS.set(entry.getKey().faceIndex * 3 + 1, uvs[1]); - resultUVS.set(entry.getKey().faceIndex * 3 + 2, uvs[2]); - } - - Image resultImage = new Image(format, resultImageWidth, resultImageHeight, BufferUtils.createByteBuffer(resultImageWidth * resultImageHeight * (format.getBitsPerPixel() >> 3))); - resultTexture = new Texture2D(resultImage); - for (Entry entry : imageLayoutData.entrySet()) { - if (!duplicatedFaceIndexes.contains(entry.getKey().faceIndex)) { - this.draw(resultImage, entry.getKey().image, entry.getValue()[0], entry.getValue()[1]); - } - } - - // setting additional data - resultTexture.setWrap(WrapAxis.S, this.getWrap(WrapAxis.S)); - resultTexture.setWrap(WrapAxis.T, this.getWrap(WrapAxis.T)); - resultTexture.setMagFilter(this.getMagFilter()); - resultTexture.setMinFilter(this.getMinFilter()); - } - return resultTexture; - } - - /** - * @return the result flat texture - */ - public Texture2D getResultTexture() { - return this.getResultTexture(false); - } - - /** - * @return the result texture's UV coordinates - */ - public List getResultUVS() { - this.getResultTexture();// this is called here to make sure that the result UVS are computed - return resultUVS; - } - - /** - * This method returns a single image element for the given face index. - * - * @param faceIndex - * the face index - * @return image element for the required face index - * @throws IllegalStateException - * this exception is thrown if the current image set does not - * contain an image for the given face index - */ - public TriangleTextureElement getFaceTextureElement(int faceIndex) { - for (TriangleTextureElement textureElement : faceTextures) { - if (textureElement.faceIndex == faceIndex) { - return textureElement; - } - } - throw new IllegalStateException("No face texture element found for index: " + faceIndex); - } - - /** - * @return the amount of texture faces - */ - public int getFaceTextureCount() { - return faceTextures.size(); - } - - /** - * Tells the object wheather to keep or reduce identical face textures. - * - * @param keepIdenticalTextures - * keeps or discards identical textures - */ - public void setKeepIdenticalTextures(boolean keepIdenticalTextures) { - this.keepIdenticalTextures = keepIdenticalTextures; - } - - /** - * This method draws the source image on the target image starting with the - * specified positions. - * - * @param target - * the target image - * @param source - * the source image - * @param targetXPos - * start X position on the target image - * @param targetYPos - * start Y position on the target image - */ - private void draw(Image target, Image source, int targetXPos, int targetYPos) { - PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(source.getFormat()); - PixelInputOutput targetIO = PixelIOFactory.getPixelIO(target.getFormat()); - TexturePixel pixel = new TexturePixel(); - - for (int x = 0; x < source.getWidth(); ++x) { - for (int y = 0; y < source.getHeight(); ++y) { - sourceIO.read(source, 0, pixel, x, y); - targetIO.write(target, 0, pixel, targetXPos + x, targetYPos + y); - } - } - } - - /** - * A class that represents an image for a single face of the mesh. - * - * @author Marcin Roguski (Kaelthas) - */ - /* package */static class TriangleTextureElement { - /** The image for the face. */ - public Image image; - /** The UV coordinates for the image. */ - public final Vector2f[] uv; - /** The index of the face this image refers to. */ - public final int faceIndex; - - /** - * Constructor that creates the image element from the given texture and - * UV coordinates (it cuts out the smallest rectasngle possible from the - * given image that will hold the triangle defined by the given UV - * coordinates). After the image is cut out the UV coordinates are - * recalculated to be fit for the new image. - * - * @param faceIndex - * the index of mesh's face this image refers to - * @param sourceImage - * the source image - * @param uvCoordinates - * the UV coordinates that define the image - */ - public TriangleTextureElement(int faceIndex, Image sourceImage, List uvCoordinates, boolean wholeUVList, BlenderContext blenderContext) { - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - this.faceIndex = faceIndex; - - uv = wholeUVList ? - new Vector2f[] { uvCoordinates.get(faceIndex * 3).clone(), uvCoordinates.get(faceIndex * 3 + 1).clone(), uvCoordinates.get(faceIndex * 3 + 2).clone() } : - new Vector2f[] { uvCoordinates.get(0).clone(), uvCoordinates.get(1).clone(), uvCoordinates.get(2).clone() }; - - // be careful here, floating point operations might cause the - // texture positions to be inapropriate - int[][] texturePosition = new int[3][2]; - for (int i = 0; i < texturePosition.length; ++i) { - texturePosition[i][0] = textureHelper.getPixelPosition(uv[i].x, sourceImage.getWidth()); - texturePosition[i][1] = textureHelper.getPixelPosition(uv[i].y, sourceImage.getHeight()); - } - - // calculating the extent of the texture - int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE; - int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE; - float minUVX = Float.MAX_VALUE, minUVY = Float.MAX_VALUE; - float maxUVX = Float.MIN_VALUE, maxUVY = Float.MIN_VALUE; - - for (int i = 0; i < texturePosition.length; ++i) { - minX = Math.min(texturePosition[i][0], minX); - minY = Math.min(texturePosition[i][1], minY); - - maxX = Math.max(texturePosition[i][0], maxX); - maxY = Math.max(texturePosition[i][1], maxY); - - minUVX = Math.min(uv[i].x, minUVX); - minUVY = Math.min(uv[i].y, minUVY); - maxUVX = Math.max(uv[i].x, maxUVX); - maxUVY = Math.max(uv[i].y, maxUVY); - } - int width = maxX - minX; - int height = maxY - minY; - - if (width == 0) { - width = 1; - } - if (height == 0) { - height = 1; - } - - // copy the pixel from the texture to the result image - PixelInputOutput pixelReader = PixelIOFactory.getPixelIO(sourceImage.getFormat()); - TexturePixel pixel = new TexturePixel(); - ByteBuffer data = BufferUtils.createByteBuffer(width * height * 4); - for (int y = minY; y < maxY; ++y) { - for (int x = minX; x < maxX; ++x) { - int xPos = x >= sourceImage.getWidth() ? x - sourceImage.getWidth() : x; - int yPos = y >= sourceImage.getHeight() ? y - sourceImage.getHeight() : y; - pixelReader.read(sourceImage, 0, pixel, xPos, yPos); - data.put(pixel.getR8()); - data.put(pixel.getG8()); - data.put(pixel.getB8()); - data.put(pixel.getA8()); - } - } - image = new Image(Format.RGBA8, width, height, data); - - // modify the UV values so that they fit the new image - float heightUV = maxUVY - minUVY; - float widthUV = maxUVX - minUVX; - for (int i = 0; i < uv.length; ++i) { - // first translate it to the image borders - uv[i].x -= minUVX; - uv[i].y -= minUVY; - // then scale so that it fills the whole area - uv[i].x /= widthUV; - uv[i].y /= heightUV; - } - } - - /** - * Constructor that creates an image element from the 3D texture - * (generated texture). It computes a flat smallest rectangle that can - * hold a (3D) triangle defined by the given UV coordinates. Then it - * defines the image pixels for points in 3D space that define the - * calculated rectangle. - * - * @param faceIndex - * the face index this image refers to - * @param boundingBox - * the bounding box of the mesh - * @param texture - * the texture that allows to compute a pixel value in 3D - * space - * @param uv - * the UV coordinates of the mesh - * @param blenderContext - * the blender context - */ - public TriangleTextureElement(int faceIndex, BoundingBox boundingBox, GeneratedTexture texture, Vector3f[] uv, int[] uvIndices, BlenderContext blenderContext) { - this.faceIndex = faceIndex; - - // compute the face vertices from the UV coordinates - float width = boundingBox.getXExtent() * 2; - float height = boundingBox.getYExtent() * 2; - float depth = boundingBox.getZExtent() * 2; - - Vector3f min = boundingBox.getMin(null); - Vector3f v1 = min.add(uv[uvIndices[0]].x * width, uv[uvIndices[0]].y * height, uv[uvIndices[0]].z * depth); - Vector3f v2 = min.add(uv[uvIndices[1]].x * width, uv[uvIndices[1]].y * height, uv[uvIndices[1]].z * depth); - Vector3f v3 = min.add(uv[uvIndices[2]].x * width, uv[uvIndices[2]].y * height, uv[uvIndices[2]].z * depth); - - // get the rectangle envelope for the triangle - RectangleEnvelope envelope = this.getTriangleEnvelope(v1, v2, v3); - - // create the result image - Format imageFormat = texture.getImage().getFormat(); - int imageWidth = (int) (envelope.width * blenderContext.getBlenderKey().getGeneratedTexturePPU()); - if(imageWidth == 0) { - imageWidth = 1; - } - int imageHeight = (int) (envelope.height * blenderContext.getBlenderKey().getGeneratedTexturePPU()); - if(imageHeight == 0) { - imageHeight = 1; - } - ByteBuffer data = BufferUtils.createByteBuffer(imageWidth * imageHeight * (imageFormat.getBitsPerPixel() >> 3)); - image = new Image(texture.getImage().getFormat(), imageWidth, imageHeight, data); - - // computing the pixels - PixelInputOutput pixelWriter = PixelIOFactory.getPixelIO(imageFormat); - TexturePixel pixel = new TexturePixel(); - float[] uvs = new float[3]; - Vector3f point = new Vector3f(envelope.min); - Vector3f vecY = new Vector3f(); - Vector3f wDelta = new Vector3f(envelope.w).multLocal(1.0f / imageWidth); - Vector3f hDelta = new Vector3f(envelope.h).multLocal(1.0f / imageHeight); - for (int x = 0; x < imageWidth; ++x) { - for (int y = 0; y < imageHeight; ++y) { - this.toTextureUV(boundingBox, point, uvs); - texture.getPixel(pixel, uvs[0], uvs[1], uvs[2]); - pixelWriter.write(image, 0, pixel, x, y); - point.addLocal(hDelta); - } - - vecY.addLocal(wDelta); - point.set(envelope.min).addLocal(vecY); - } - - // preparing UV coordinates for the flatted texture - this.uv = new Vector2f[3]; - this.uv[0] = new Vector2f(FastMath.clamp(v1.subtract(envelope.min).length(), 0, Float.MAX_VALUE) / envelope.height, 0); - Vector3f heightDropPoint = v2.subtract(envelope.w);// w is directed from the base to v2 - this.uv[1] = new Vector2f(1, heightDropPoint.subtractLocal(envelope.min).length() / envelope.height); - this.uv[2] = new Vector2f(0, 1); - } - - /** - * This method computes the final UV coordinates for the image (after it - * is combined with other images and drawed on the result image). - * - * @param totalImageWidth - * the result image width - * @param totalImageHeight - * the result image height - * @param xPos - * the most left x coordinate of the image - * @param yPos - * the most top y coordinate of the image - * @param result - * a vector where the result is stored - */ - public void computeFinalUVCoordinates(int totalImageWidth, int totalImageHeight, int xPos, int yPos, Vector2f[] result) { - for (int i = 0; i < 3; ++i) { - result[i] = new Vector2f(); - result[i].x = xPos / (float) totalImageWidth + this.uv[i].x * (this.image.getWidth() / (float) totalImageWidth); - result[i].y = yPos / (float) totalImageHeight + this.uv[i].y * (this.image.getHeight() / (float) totalImageHeight); - } - } - - /** - * This method converts the given point into 3D UV coordinates. - * - * @param boundingBox - * the bounding box of the mesh - * @param point - * the point to be transformed - * @param uvs - * the result UV coordinates - */ - private void toTextureUV(BoundingBox boundingBox, Vector3f point, float[] uvs) { - uvs[0] = (point.x - boundingBox.getCenter().x)/(boundingBox.getXExtent() == 0 ? 1 : boundingBox.getXExtent()); - uvs[1] = (point.y - boundingBox.getCenter().y)/(boundingBox.getYExtent() == 0 ? 1 : boundingBox.getYExtent()); - uvs[2] = (point.z - boundingBox.getCenter().z)/(boundingBox.getZExtent() == 0 ? 1 : boundingBox.getZExtent()); - //UVS cannot go outside <0, 1> range, but since we are generating texture for triangle envelope it might happen that - //some points of the envelope will exceet the bounding box of the mesh thus generating uvs outside the range - for (int i = 0; i < 3; ++i) { - uvs[i] = FastMath.clamp(uvs[i], 0, 1); - } - } - - /** - * This method returns an envelope of a minimal rectangle, that is set - * in 3D space, and contains the given triangle. - * - * @param triangle - * the triangle - * @return a rectangle minimum and maximum point and height and width - */ - private RectangleEnvelope getTriangleEnvelope(Vector3f v1, Vector3f v2, Vector3f v3) { - Vector3f h = v3.subtract(v1);// the height of the resulting rectangle - Vector3f temp = v2.subtract(v1); - - float field = 0.5f * h.cross(temp).length();// the field of the rectangle: Field = 0.5 * ||h x temp|| - if (field <= 0.0f) { - return new RectangleEnvelope(v1);// return single point envelope - } - - float cosAlpha = h.dot(temp) / (h.length() * temp.length());// the cosinus of angle betweenh and temp - - float triangleHeight = 2 * field / h.length();// the base of the height is the h vector - // now calculate the distance between v1 vertex and the point where - // the above calculated height 'touches' the base line (it can be - // settled outside the h vector) - float x = Math.abs((float) Math.sqrt(FastMath.clamp(temp.lengthSquared() - triangleHeight * triangleHeight, 0, Float.MAX_VALUE))) * Math.signum(cosAlpha); - // now get the height base point - Vector3f xPoint = v1.add(h.normalize().multLocal(x)); - - // get the minimum point of the envelope - Vector3f min = x < 0 ? xPoint : v1; - if (x < 0) { - h = v3.subtract(min); - } else if (x > h.length()) { - h = xPoint.subtract(min); - } - - Vector3f envelopeWidth = v2.subtract(xPoint); - return new RectangleEnvelope(min, envelopeWidth, h); - } - } - - /** - * A class that represents a flat rectangle in 3D space that is built on a - * triangle in 3D space. - * - * @author Marcin Roguski (Kaelthas) - */ - private static class RectangleEnvelope { - /** The minimum point of the rectangle. */ - public final Vector3f min; - /** The width vector. */ - public final Vector3f w; - /** The height vector. */ - public final Vector3f h; - /** The width of the rectangle. */ - public final float width; - /** The height of the rectangle. */ - public final float height; - - /** - * Constructs a rectangle that actually holds a point, not a triangle. - * This is a special case that is sometimes used when generating a - * texture where UV coordinates are defined by normals instead of - * vertices. - * - * @param pointPosition - * a position in 3D space - */ - public RectangleEnvelope(Vector3f pointPosition) { - this.min = pointPosition; - this.h = this.w = Vector3f.ZERO; - this.width = this.height = 1; - } - - /** - * Constructs a rectangle envelope. - * - * @param min - * the minimum rectangle point - * @param w - * the width vector - * @param h - * the height vector - */ - public RectangleEnvelope(Vector3f min, Vector3f w, Vector3f h) { - this.min = min; - this.h = h; - this.w = w; - this.width = w.length(); - this.height = h.length(); - } - - @Override - public String toString() { - return "Envelope[min = " + min + ", w = " + w + ", h = " + h + "]"; - } - } - - @Override - public Texture createSimpleClone() { - return null; - } + /** The result image format. */ + private Format format; + /** The collection of images for each face. */ + private Collection faceTextures; + /** + * The maximum texture size (width/height). This is taken from the blender + * key. + */ + private int maxTextureSize; + /** A variable that can prevent removing identical textures. */ + private boolean keepIdenticalTextures = false; + /** The result texture. */ + private Texture2D resultTexture; + /** The result texture's UV coordinates. */ + private List resultUVS; + + /** + * This method triangulates the given flat texture. The given texture is not + * changed. + * + * @param texture2d + * the texture to be triangulated + * @param uvs + * the UV coordinates for each face + */ + public TriangulatedTexture(Texture2D texture2d, List uvs, BlenderContext blenderContext) { + maxTextureSize = blenderContext.getBlenderKey().getMaxTextureSize(); + faceTextures = new TreeSet(new Comparator() { + public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { + return o1.faceIndex - o2.faceIndex; + } + }); + int facesCount = uvs.size() / 3; + for (int i = 0; i < facesCount; ++i) { + faceTextures.add(new TriangleTextureElement(i, texture2d.getImage(), uvs, true, blenderContext)); + } + this.format = texture2d.getImage().getFormat(); + } + + /** + * Constructor that simply stores precalculated images. + * + * @param faceTextures + * a collection of images for the mesh's faces + * @param blenderContext + * the blender context + */ + public TriangulatedTexture(Collection faceTextures, BlenderContext blenderContext) { + maxTextureSize = blenderContext.getBlenderKey().getMaxTextureSize(); + this.faceTextures = faceTextures; + for (TriangleTextureElement faceTextureElement : faceTextures) { + if (format == null) { + format = faceTextureElement.image.getFormat(); + } else if (format != faceTextureElement.image.getFormat()) { + throw new IllegalArgumentException("Face texture element images MUST have the same image format!"); + } + } + } + + /** + * This method blends the each image using the given blender and taking base + * texture into consideration. + * + * @param textureBlender + * the texture blender that holds the blending definition + * @param baseTexture + * the texture that is 'below' the current texture (can be null) + * @param blenderContext + * the blender context + */ + public void blend(TextureBlender textureBlender, TriangulatedTexture baseTexture, BlenderContext blenderContext) { + Format newFormat = null; + for (TriangleTextureElement triangleTextureElement : this.faceTextures) { + Image baseImage = baseTexture == null ? null : baseTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image; + triangleTextureElement.image = textureBlender.blend(triangleTextureElement.image, baseImage, blenderContext); + if (newFormat == null) { + newFormat = triangleTextureElement.image.getFormat(); + } else if (newFormat != triangleTextureElement.image.getFormat()) { + throw new IllegalArgumentException("Face texture element images MUST have the same image format!"); + } + } + this.format = newFormat; + } + + /** + * This method alters the images to fit them into UV coordinates of the + * given target texture. + * + * @param targetTexture + * the texture to whose UV coordinates we fit current images + * @param blenderContext + * the blender context + */ + public void castToUVS(TriangulatedTexture targetTexture, BlenderContext blenderContext) { + int[] sourceSize = new int[2], targetSize = new int[2]; + ImageLoader imageLoader = new ImageLoader(); + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + for (TriangleTextureElement entry : faceTextures) { + TriangleTextureElement targetFaceTextureElement = targetTexture.getFaceTextureElement(entry.faceIndex); + Vector2f[] dest = targetFaceTextureElement.uv; + + // get the sizes of the source and target images + sourceSize[0] = entry.image.getWidth(); + sourceSize[1] = entry.image.getHeight(); + targetSize[0] = targetFaceTextureElement.image.getWidth(); + targetSize[1] = targetFaceTextureElement.image.getHeight(); + + // create triangle transformation + AffineTransform affineTransform = textureHelper.createAffineTransform(entry.uv, dest, sourceSize, targetSize); + + // compute the result texture + BufferedImage sourceImage = ImageToAwt.convert(entry.image, false, true, 0); + + BufferedImage targetImage = new BufferedImage(targetSize[0], targetSize[1], sourceImage.getType()); + Graphics2D g = targetImage.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g.drawImage(sourceImage, affineTransform, null); + g.dispose(); + + Image output = imageLoader.load(targetImage, false); + entry.image = output; + entry.uv[0].set(dest[0]); + entry.uv[1].set(dest[1]); + entry.uv[2].set(dest[2]); + } + } + + /** + * This method merges the current texture with the given one. The given + * texture is not changed. + * + * @param triangulatedTexture + * the texture we merge current texture with + */ + public void merge(TriangulatedTexture triangulatedTexture) { + TexturePixel sourcePixel = new TexturePixel(); + TexturePixel targetPixel = new TexturePixel(); + for (TriangleTextureElement triangleTextureElement : this.faceTextures) { + Image sourceImage = triangulatedTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image; + Image targetImage = triangleTextureElement.image; + PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); + PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); + + for (int x = 0; x < sourceImage.getWidth(); ++x) { + for (int y = 0; y < sourceImage.getHeight(); ++y) { + sourceIO.read(sourceImage, 0, sourcePixel, x, y); + targetIO.read(targetImage, 0, targetPixel, x, y); + targetPixel.merge(sourcePixel); + targetIO.write(targetImage, 0, targetPixel, x, y); + } + } + } + } + + /** + * This method returns the flat texture. It is calculated if required or if + * it was not created before. Images that are identical are discarded to + * reduce the texture size. + * + * @param rebuild + * a variable that forces texture recomputation (even if it was + * computed vefore) + * @return flat result texture (all images merged into one) + */ + public Texture2D getResultTexture(boolean rebuild) { + if (resultTexture == null || rebuild) { + // sorting the parts by their height (from highest to the lowest) + List list = new ArrayList(faceTextures); + Collections.sort(list, new Comparator() { + public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { + return o2.image.getHeight() - o1.image.getHeight(); + } + }); + + // arraging the images on the resulting image (calculating the result image width and height) + Set duplicatedFaceIndexes = new HashSet(); + int resultImageHeight = list.get(0).image.getHeight(); + int resultImageWidth = 0; + int currentXPos = 0, currentYPos = 0; + Map imageLayoutData = new HashMap(list.size()); + while (list.size() > 0) { + TriangleTextureElement currentElement = list.remove(0); + if (currentXPos + currentElement.image.getWidth() > maxTextureSize) { + currentXPos = 0; + currentYPos = resultImageHeight; + resultImageHeight += currentElement.image.getHeight(); + } + Integer[] currentPositions = new Integer[] { currentXPos, currentYPos }; + imageLayoutData.put(currentElement, currentPositions); + + if (keepIdenticalTextures) {// removing identical images + for (int i = 0; i < list.size(); ++i) { + if (currentElement.image.equals(list.get(i).image)) { + duplicatedFaceIndexes.add(list.get(i).faceIndex); + imageLayoutData.put(list.remove(i--), currentPositions); + } + } + } + + currentXPos += currentElement.image.getWidth(); + resultImageWidth = Math.max(resultImageWidth, currentXPos); + // currentYPos += currentElement.image.getHeight(); + + // TODO: implement that to compact the result image + // try to add smaller images below the current one + // int remainingHeight = resultImageHeight - + // currentElement.image.getHeight(); + // while(remainingHeight > 0) { + // for(int i=list.size() - 1;i>=0;--i) { + // + // } + // } + } + + // computing the result UV coordinates + resultUVS = new ArrayList(imageLayoutData.size() * 3); + for (int i = 0; i < imageLayoutData.size() * 3; ++i) { + resultUVS.add(null); + } + Vector2f[] uvs = new Vector2f[3]; + for (Entry entry : imageLayoutData.entrySet()) { + Integer[] position = entry.getValue(); + entry.getKey().computeFinalUVCoordinates(resultImageWidth, resultImageHeight, position[0], position[1], uvs); + resultUVS.set(entry.getKey().faceIndex * 3, uvs[0]); + resultUVS.set(entry.getKey().faceIndex * 3 + 1, uvs[1]); + resultUVS.set(entry.getKey().faceIndex * 3 + 2, uvs[2]); + } + + Image resultImage = new Image(format, resultImageWidth, resultImageHeight, BufferUtils.createByteBuffer(resultImageWidth * resultImageHeight * (format.getBitsPerPixel() >> 3))); + resultTexture = new Texture2D(resultImage); + for (Entry entry : imageLayoutData.entrySet()) { + if (!duplicatedFaceIndexes.contains(entry.getKey().faceIndex)) { + this.draw(resultImage, entry.getKey().image, entry.getValue()[0], entry.getValue()[1]); + } + } + + // setting additional data + resultTexture.setWrap(WrapAxis.S, this.getWrap(WrapAxis.S)); + resultTexture.setWrap(WrapAxis.T, this.getWrap(WrapAxis.T)); + resultTexture.setMagFilter(this.getMagFilter()); + resultTexture.setMinFilter(this.getMinFilter()); + } + return resultTexture; + } + + /** + * @return the result flat texture + */ + public Texture2D getResultTexture() { + return this.getResultTexture(false); + } + + /** + * @return the result texture's UV coordinates + */ + public List getResultUVS() { + this.getResultTexture();// this is called here to make sure that the result UVS are computed + return resultUVS; + } + + /** + * This method returns a single image element for the given face index. + * + * @param faceIndex + * the face index + * @return image element for the required face index + * @throws IllegalStateException + * this exception is thrown if the current image set does not + * contain an image for the given face index + */ + public TriangleTextureElement getFaceTextureElement(int faceIndex) { + for (TriangleTextureElement textureElement : faceTextures) { + if (textureElement.faceIndex == faceIndex) { + return textureElement; + } + } + throw new IllegalStateException("No face texture element found for index: " + faceIndex); + } + + /** + * @return the amount of texture faces + */ + public int getFaceTextureCount() { + return faceTextures.size(); + } + + /** + * Tells the object wheather to keep or reduce identical face textures. + * + * @param keepIdenticalTextures + * keeps or discards identical textures + */ + public void setKeepIdenticalTextures(boolean keepIdenticalTextures) { + this.keepIdenticalTextures = keepIdenticalTextures; + } + + /** + * This method draws the source image on the target image starting with the + * specified positions. + * + * @param target + * the target image + * @param source + * the source image + * @param targetXPos + * start X position on the target image + * @param targetYPos + * start Y position on the target image + */ + private void draw(Image target, Image source, int targetXPos, int targetYPos) { + PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(source.getFormat()); + PixelInputOutput targetIO = PixelIOFactory.getPixelIO(target.getFormat()); + TexturePixel pixel = new TexturePixel(); + + for (int x = 0; x < source.getWidth(); ++x) { + for (int y = 0; y < source.getHeight(); ++y) { + sourceIO.read(source, 0, pixel, x, y); + targetIO.write(target, 0, pixel, targetXPos + x, targetYPos + y); + } + } + } + + /** + * A class that represents an image for a single face of the mesh. + * + * @author Marcin Roguski (Kaelthas) + */ + /* package */static class TriangleTextureElement { + /** The image for the face. */ + public Image image; + /** The UV coordinates for the image. */ + public final Vector2f[] uv; + /** The index of the face this image refers to. */ + public final int faceIndex; + + /** + * Constructor that creates the image element from the given texture and + * UV coordinates (it cuts out the smallest rectasngle possible from the + * given image that will hold the triangle defined by the given UV + * coordinates). After the image is cut out the UV coordinates are + * recalculated to be fit for the new image. + * + * @param faceIndex + * the index of mesh's face this image refers to + * @param sourceImage + * the source image + * @param uvCoordinates + * the UV coordinates that define the image + */ + public TriangleTextureElement(int faceIndex, Image sourceImage, List uvCoordinates, boolean wholeUVList, BlenderContext blenderContext) { + TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); + this.faceIndex = faceIndex; + + uv = wholeUVList ? new Vector2f[] { uvCoordinates.get(faceIndex * 3).clone(), uvCoordinates.get(faceIndex * 3 + 1).clone(), uvCoordinates.get(faceIndex * 3 + 2).clone() } : new Vector2f[] { uvCoordinates.get(0).clone(), uvCoordinates.get(1).clone(), uvCoordinates.get(2).clone() }; + + // be careful here, floating point operations might cause the + // texture positions to be inapropriate + int[][] texturePosition = new int[3][2]; + for (int i = 0; i < texturePosition.length; ++i) { + texturePosition[i][0] = textureHelper.getPixelPosition(uv[i].x, sourceImage.getWidth()); + texturePosition[i][1] = textureHelper.getPixelPosition(uv[i].y, sourceImage.getHeight()); + } + + // calculating the extent of the texture + int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE; + int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE; + float minUVX = Float.MAX_VALUE, minUVY = Float.MAX_VALUE; + float maxUVX = Float.MIN_VALUE, maxUVY = Float.MIN_VALUE; + + for (int i = 0; i < texturePosition.length; ++i) { + minX = Math.min(texturePosition[i][0], minX); + minY = Math.min(texturePosition[i][1], minY); + + maxX = Math.max(texturePosition[i][0], maxX); + maxY = Math.max(texturePosition[i][1], maxY); + + minUVX = Math.min(uv[i].x, minUVX); + minUVY = Math.min(uv[i].y, minUVY); + maxUVX = Math.max(uv[i].x, maxUVX); + maxUVY = Math.max(uv[i].y, maxUVY); + } + int width = maxX - minX; + int height = maxY - minY; + + if (width == 0) { + width = 1; + } + if (height == 0) { + height = 1; + } + + // copy the pixel from the texture to the result image + PixelInputOutput pixelReader = PixelIOFactory.getPixelIO(sourceImage.getFormat()); + TexturePixel pixel = new TexturePixel(); + ByteBuffer data = BufferUtils.createByteBuffer(width * height * 4); + for (int y = minY; y < maxY; ++y) { + for (int x = minX; x < maxX; ++x) { + int xPos = x >= sourceImage.getWidth() ? x - sourceImage.getWidth() : x; + int yPos = y >= sourceImage.getHeight() ? y - sourceImage.getHeight() : y; + pixelReader.read(sourceImage, 0, pixel, xPos, yPos); + data.put(pixel.getR8()); + data.put(pixel.getG8()); + data.put(pixel.getB8()); + data.put(pixel.getA8()); + } + } + image = new Image(Format.RGBA8, width, height, data); + + // modify the UV values so that they fit the new image + float heightUV = maxUVY - minUVY; + float widthUV = maxUVX - minUVX; + for (int i = 0; i < uv.length; ++i) { + // first translate it to the image borders + uv[i].x -= minUVX; + uv[i].y -= minUVY; + // then scale so that it fills the whole area + uv[i].x /= widthUV; + uv[i].y /= heightUV; + } + } + + /** + * Constructor that creates an image element from the 3D texture + * (generated texture). It computes a flat smallest rectangle that can + * hold a (3D) triangle defined by the given UV coordinates. Then it + * defines the image pixels for points in 3D space that define the + * calculated rectangle. + * + * @param faceIndex + * the face index this image refers to + * @param boundingBox + * the bounding box of the mesh + * @param texture + * the texture that allows to compute a pixel value in 3D + * space + * @param uv + * the UV coordinates of the mesh + * @param blenderContext + * the blender context + */ + public TriangleTextureElement(int faceIndex, BoundingBox boundingBox, GeneratedTexture texture, Vector3f[] uv, int[] uvIndices, BlenderContext blenderContext) { + this.faceIndex = faceIndex; + + // compute the face vertices from the UV coordinates + float width = boundingBox.getXExtent() * 2; + float height = boundingBox.getYExtent() * 2; + float depth = boundingBox.getZExtent() * 2; + + Vector3f min = boundingBox.getMin(null); + Vector3f v1 = min.add(uv[uvIndices[0]].x * width, uv[uvIndices[0]].y * height, uv[uvIndices[0]].z * depth); + Vector3f v2 = min.add(uv[uvIndices[1]].x * width, uv[uvIndices[1]].y * height, uv[uvIndices[1]].z * depth); + Vector3f v3 = min.add(uv[uvIndices[2]].x * width, uv[uvIndices[2]].y * height, uv[uvIndices[2]].z * depth); + + // get the rectangle envelope for the triangle + RectangleEnvelope envelope = this.getTriangleEnvelope(v1, v2, v3); + + // create the result image + Format imageFormat = texture.getImage().getFormat(); + int imageWidth = (int) (envelope.width * blenderContext.getBlenderKey().getGeneratedTexturePPU()); + if (imageWidth == 0) { + imageWidth = 1; + } + int imageHeight = (int) (envelope.height * blenderContext.getBlenderKey().getGeneratedTexturePPU()); + if (imageHeight == 0) { + imageHeight = 1; + } + ByteBuffer data = BufferUtils.createByteBuffer(imageWidth * imageHeight * (imageFormat.getBitsPerPixel() >> 3)); + image = new Image(texture.getImage().getFormat(), imageWidth, imageHeight, data); + + // computing the pixels + PixelInputOutput pixelWriter = PixelIOFactory.getPixelIO(imageFormat); + TexturePixel pixel = new TexturePixel(); + float[] uvs = new float[3]; + Vector3f point = new Vector3f(envelope.min); + Vector3f vecY = new Vector3f(); + Vector3f wDelta = new Vector3f(envelope.w).multLocal(1.0f / imageWidth); + Vector3f hDelta = new Vector3f(envelope.h).multLocal(1.0f / imageHeight); + for (int x = 0; x < imageWidth; ++x) { + for (int y = 0; y < imageHeight; ++y) { + this.toTextureUV(boundingBox, point, uvs); + texture.getPixel(pixel, uvs[0], uvs[1], uvs[2]); + pixelWriter.write(image, 0, pixel, x, y); + point.addLocal(hDelta); + } + + vecY.addLocal(wDelta); + point.set(envelope.min).addLocal(vecY); + } + + // preparing UV coordinates for the flatted texture + this.uv = new Vector2f[3]; + this.uv[0] = new Vector2f(FastMath.clamp(v1.subtract(envelope.min).length(), 0, Float.MAX_VALUE) / envelope.height, 0); + Vector3f heightDropPoint = v2.subtract(envelope.w);// w is directed from the base to v2 + this.uv[1] = new Vector2f(1, heightDropPoint.subtractLocal(envelope.min).length() / envelope.height); + this.uv[2] = new Vector2f(0, 1); + } + + /** + * This method computes the final UV coordinates for the image (after it + * is combined with other images and drawed on the result image). + * + * @param totalImageWidth + * the result image width + * @param totalImageHeight + * the result image height + * @param xPos + * the most left x coordinate of the image + * @param yPos + * the most top y coordinate of the image + * @param result + * a vector where the result is stored + */ + public void computeFinalUVCoordinates(int totalImageWidth, int totalImageHeight, int xPos, int yPos, Vector2f[] result) { + for (int i = 0; i < 3; ++i) { + result[i] = new Vector2f(); + result[i].x = xPos / (float) totalImageWidth + this.uv[i].x * (this.image.getWidth() / (float) totalImageWidth); + result[i].y = yPos / (float) totalImageHeight + this.uv[i].y * (this.image.getHeight() / (float) totalImageHeight); + } + } + + /** + * This method converts the given point into 3D UV coordinates. + * + * @param boundingBox + * the bounding box of the mesh + * @param point + * the point to be transformed + * @param uvs + * the result UV coordinates + */ + private void toTextureUV(BoundingBox boundingBox, Vector3f point, float[] uvs) { + uvs[0] = (point.x - boundingBox.getCenter().x) / (boundingBox.getXExtent() == 0 ? 1 : boundingBox.getXExtent()); + uvs[1] = (point.y - boundingBox.getCenter().y) / (boundingBox.getYExtent() == 0 ? 1 : boundingBox.getYExtent()); + uvs[2] = (point.z - boundingBox.getCenter().z) / (boundingBox.getZExtent() == 0 ? 1 : boundingBox.getZExtent()); + // UVS cannot go outside <0, 1> range, but since we are generating texture for triangle envelope it might happen that + // some points of the envelope will exceet the bounding box of the mesh thus generating uvs outside the range + for (int i = 0; i < 3; ++i) { + uvs[i] = FastMath.clamp(uvs[i], 0, 1); + } + } + + /** + * This method returns an envelope of a minimal rectangle, that is set + * in 3D space, and contains the given triangle. + * + * @param triangle + * the triangle + * @return a rectangle minimum and maximum point and height and width + */ + private RectangleEnvelope getTriangleEnvelope(Vector3f v1, Vector3f v2, Vector3f v3) { + Vector3f h = v3.subtract(v1);// the height of the resulting rectangle + Vector3f temp = v2.subtract(v1); + + float field = 0.5f * h.cross(temp).length();// the field of the rectangle: Field = 0.5 * ||h x temp|| + if (field <= 0.0f) { + return new RectangleEnvelope(v1);// return single point envelope + } + + float cosAlpha = h.dot(temp) / (h.length() * temp.length());// the cosinus of angle betweenh and temp + + float triangleHeight = 2 * field / h.length();// the base of the height is the h vector + // now calculate the distance between v1 vertex and the point where + // the above calculated height 'touches' the base line (it can be + // settled outside the h vector) + float x = Math.abs((float) Math.sqrt(FastMath.clamp(temp.lengthSquared() - triangleHeight * triangleHeight, 0, Float.MAX_VALUE))) * Math.signum(cosAlpha); + // now get the height base point + Vector3f xPoint = v1.add(h.normalize().multLocal(x)); + + // get the minimum point of the envelope + Vector3f min = x < 0 ? xPoint : v1; + if (x < 0) { + h = v3.subtract(min); + } else if (x > h.length()) { + h = xPoint.subtract(min); + } + + Vector3f envelopeWidth = v2.subtract(xPoint); + return new RectangleEnvelope(min, envelopeWidth, h); + } + } + + /** + * A class that represents a flat rectangle in 3D space that is built on a + * triangle in 3D space. + * + * @author Marcin Roguski (Kaelthas) + */ + private static class RectangleEnvelope { + /** The minimum point of the rectangle. */ + public final Vector3f min; + /** The width vector. */ + public final Vector3f w; + /** The height vector. */ + public final Vector3f h; + /** The width of the rectangle. */ + public final float width; + /** The height of the rectangle. */ + public final float height; + + /** + * Constructs a rectangle that actually holds a point, not a triangle. + * This is a special case that is sometimes used when generating a + * texture where UV coordinates are defined by normals instead of + * vertices. + * + * @param pointPosition + * a position in 3D space + */ + public RectangleEnvelope(Vector3f pointPosition) { + this.min = pointPosition; + this.h = this.w = Vector3f.ZERO; + this.width = this.height = 1; + } + + /** + * Constructs a rectangle envelope. + * + * @param min + * the minimum rectangle point + * @param w + * the width vector + * @param h + * the height vector + */ + public RectangleEnvelope(Vector3f min, Vector3f w, Vector3f h) { + this.min = min; + this.h = h; + this.w = w; + this.width = w.length(); + this.height = h.length(); + } + + @Override + public String toString() { + return "Envelope[min = " + min + ", w = " + w + ", h = " + h + "]"; + } + } + + @Override + public Texture createSimpleClone() { + return null; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java index cdcf7d4e0..cde876b46 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java @@ -52,442 +52,428 @@ import java.util.logging.Logger; * @author Marcin Roguski (Kaelthas) */ public class UVCoordinatesGenerator { - private static final Logger LOGGER = Logger.getLogger(UVCoordinatesGenerator.class.getName()); + private static final Logger LOGGER = Logger.getLogger(UVCoordinatesGenerator.class.getName()); - public static enum UVCoordinatesType { - TEXCO_ORCO(1), - TEXCO_REFL(2), - TEXCO_NORM(4), - TEXCO_GLOB(8), - TEXCO_UV(16), - TEXCO_OBJECT(32), - TEXCO_LAVECTOR(64), - TEXCO_VIEW(128), - TEXCO_STICKY(256), - TEXCO_OSA(512), - TEXCO_WINDOW(1024), - NEED_UV(2048), - TEXCO_TANGENT(4096), - // still stored in vertex->accum, 1 D - TEXCO_PARTICLE_OR_STRAND(8192), - TEXCO_STRESS(16384), - TEXCO_SPEED(32768); + public static enum UVCoordinatesType { + TEXCO_ORCO(1), TEXCO_REFL(2), TEXCO_NORM(4), TEXCO_GLOB(8), TEXCO_UV(16), TEXCO_OBJECT(32), TEXCO_LAVECTOR(64), TEXCO_VIEW(128), TEXCO_STICKY(256), TEXCO_OSA(512), TEXCO_WINDOW(1024), NEED_UV(2048), TEXCO_TANGENT(4096), + // still stored in vertex->accum, 1 D + TEXCO_PARTICLE_OR_STRAND(8192), TEXCO_STRESS(16384), TEXCO_SPEED(32768); - public final int blenderValue; + public final int blenderValue; - private UVCoordinatesType(int blenderValue) { - this.blenderValue = blenderValue; - } + private UVCoordinatesType(int blenderValue) { + this.blenderValue = blenderValue; + } - public static UVCoordinatesType valueOf(int blenderValue) { - for (UVCoordinatesType coordinatesType : UVCoordinatesType.values()) { - if (coordinatesType.blenderValue == blenderValue) { - return coordinatesType; - } - } - return null; - } - } + public static UVCoordinatesType valueOf(int blenderValue) { + for (UVCoordinatesType coordinatesType : UVCoordinatesType.values()) { + if (coordinatesType.blenderValue == blenderValue) { + return coordinatesType; + } + } + return null; + } + } - /** - * Generates a UV coordinates for 2D texture. - * - * @param mesh - * the mesh we generate UV's for - * @param texco - * UV coordinates type - * @param projection - * projection type - * @param geometries - * the geometris the given mesh belongs to (required to compute - * bounding box) - * @return UV coordinates for the given mesh - */ - public static List generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, List geometries) { - List result = new ArrayList(); - BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); - float[] inputData = null;// positions, normals, reflection vectors, etc. + /** + * Generates a UV coordinates for 2D texture. + * + * @param mesh + * the mesh we generate UV's for + * @param texco + * UV coordinates type + * @param projection + * projection type + * @param geometries + * the geometris the given mesh belongs to (required to compute + * bounding box) + * @return UV coordinates for the given mesh + */ + public static List generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, List geometries) { + List result = new ArrayList(); + BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); + float[] inputData = null;// positions, normals, reflection vectors, etc. - switch (texco) { - case TEXCO_ORCO: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); - break; - case TEXCO_UV:// this should be used if not defined by user explicitly - Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; - for (int i = 0; i < mesh.getVertexCount(); ++i) { - result.add(data[i % 3]); - } - break; - case TEXCO_NORM: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); - break; - case TEXCO_REFL: - case TEXCO_GLOB: - case TEXCO_TANGENT: - case TEXCO_STRESS: - case TEXCO_LAVECTOR: - case TEXCO_OBJECT: - case TEXCO_OSA: - case TEXCO_PARTICLE_OR_STRAND: - case TEXCO_SPEED: - case TEXCO_STICKY: - case TEXCO_VIEW: - case TEXCO_WINDOW: - LOGGER.warning("Texture coordinates type not currently supported: " + texco); - break; - default: - throw new IllegalStateException("Unknown texture coordinates value: " + texco); - } + switch (texco) { + case TEXCO_ORCO: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); + break; + case TEXCO_UV:// this should be used if not defined by user explicitly + Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; + for (int i = 0; i < mesh.getVertexCount(); ++i) { + result.add(data[i % 3]); + } + break; + case TEXCO_NORM: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); + break; + case TEXCO_REFL: + case TEXCO_GLOB: + case TEXCO_TANGENT: + case TEXCO_STRESS: + case TEXCO_LAVECTOR: + case TEXCO_OBJECT: + case TEXCO_OSA: + case TEXCO_PARTICLE_OR_STRAND: + case TEXCO_SPEED: + case TEXCO_STICKY: + case TEXCO_VIEW: + case TEXCO_WINDOW: + LOGGER.warning("Texture coordinates type not currently supported: " + texco); + break; + default: + throw new IllegalStateException("Unknown texture coordinates value: " + texco); + } - if (inputData != null) {// make projection calculations - switch (projection) { - case PROJECTION_FLAT: - inputData = UVProjectionGenerator.flatProjection(inputData, bb); - break; - case PROJECTION_CUBE: - inputData = UVProjectionGenerator.cubeProjection(inputData, bb); - break; - case PROJECTION_TUBE: - BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries); - inputData = UVProjectionGenerator.tubeProjection(inputData, bt); - break; - case PROJECTION_SPHERE: - BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries); - inputData = UVProjectionGenerator.sphereProjection(inputData, bs); - break; - default: - throw new IllegalStateException("Unknown projection type: " + projection); - } - for (int i = 0; i < inputData.length; i += 2) { - result.add(new Vector2f(inputData[i], inputData[i + 1])); - } - } - return result; - } + if (inputData != null) {// make projection calculations + switch (projection) { + case PROJECTION_FLAT: + inputData = UVProjectionGenerator.flatProjection(inputData, bb); + break; + case PROJECTION_CUBE: + inputData = UVProjectionGenerator.cubeProjection(inputData, bb); + break; + case PROJECTION_TUBE: + BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries); + inputData = UVProjectionGenerator.tubeProjection(inputData, bt); + break; + case PROJECTION_SPHERE: + BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries); + inputData = UVProjectionGenerator.sphereProjection(inputData, bs); + break; + default: + throw new IllegalStateException("Unknown projection type: " + projection); + } + for (int i = 0; i < inputData.length; i += 2) { + result.add(new Vector2f(inputData[i], inputData[i + 1])); + } + } + return result; + } - /** - * Generates a UV coordinates for 3D texture. - * - * @param mesh - * the mesh we generate UV's for - * @param texco - * UV coordinates type - * @param coordinatesSwappingIndexes - * coordinates swapping indexes - * @param geometries - * the geometris the given mesh belongs to (required to compute - * bounding box) - * @return UV coordinates for the given mesh - */ - public static List generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, List geometries) { - List result = new ArrayList(); - BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); - float[] inputData = null;// positions, normals, reflection vectors, etc. + /** + * Generates a UV coordinates for 3D texture. + * + * @param mesh + * the mesh we generate UV's for + * @param texco + * UV coordinates type + * @param coordinatesSwappingIndexes + * coordinates swapping indexes + * @param geometries + * the geometris the given mesh belongs to (required to compute + * bounding box) + * @return UV coordinates for the given mesh + */ + public static List generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, List geometries) { + List result = new ArrayList(); + BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); + float[] inputData = null;// positions, normals, reflection vectors, etc. - switch (texco) { - case TEXCO_ORCO: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); - break; - case TEXCO_UV: - Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; - for (int i = 0; i < mesh.getVertexCount(); ++i) { - Vector2f uv = data[i % 3]; - result.add(new Vector3f(uv.x, uv.y, 0)); - } - break; - case TEXCO_NORM: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); - break; - case TEXCO_REFL: - case TEXCO_GLOB: - case TEXCO_TANGENT: - case TEXCO_STRESS: - case TEXCO_LAVECTOR: - case TEXCO_OBJECT: - case TEXCO_OSA: - case TEXCO_PARTICLE_OR_STRAND: - case TEXCO_SPEED: - case TEXCO_STICKY: - case TEXCO_VIEW: - case TEXCO_WINDOW: - LOGGER.warning("Texture coordinates type not currently supported: " + texco); - break; - default: - throw new IllegalStateException("Unknown texture coordinates value: " + texco); - } + switch (texco) { + case TEXCO_ORCO: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); + break; + case TEXCO_UV: + Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; + for (int i = 0; i < mesh.getVertexCount(); ++i) { + Vector2f uv = data[i % 3]; + result.add(new Vector3f(uv.x, uv.y, 0)); + } + break; + case TEXCO_NORM: + inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); + break; + case TEXCO_REFL: + case TEXCO_GLOB: + case TEXCO_TANGENT: + case TEXCO_STRESS: + case TEXCO_LAVECTOR: + case TEXCO_OBJECT: + case TEXCO_OSA: + case TEXCO_PARTICLE_OR_STRAND: + case TEXCO_SPEED: + case TEXCO_STICKY: + case TEXCO_VIEW: + case TEXCO_WINDOW: + LOGGER.warning("Texture coordinates type not currently supported: " + texco); + break; + default: + throw new IllegalStateException("Unknown texture coordinates value: " + texco); + } - if (inputData != null) {// make calculations - Vector3f min = bb.getMin(null); - float[] uvCoordsResults = new float[4];// used for coordinates swapping - float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 }; - for (int i = 0; i < ext.length; ++i) { - if (ext[i] == 0) { - ext[i] = 1; - } - } - // now transform the coordinates so that they are in the range of - // <0; 1> - for (int i = 0; i < inputData.length; i += 3) { - uvCoordsResults[1] = (inputData[i] - min.x) / ext[0]; - uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1]; - uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2]; - result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]])); - } - } - return result; - } - - /** - * This method should be used to determine if the texture will ever be - * computed. If the texture coordinates are not supported then the try of - * flattening the texture might result in runtime exceptions occurence. - * - * @param texco - * the texture coordinates type - * @return true if the type is supported and false otherwise - */ - public static boolean isTextureCoordinateTypeSupported(UVCoordinatesType texco) { - switch (texco) { - case TEXCO_ORCO: - case TEXCO_UV: - case TEXCO_NORM: - return true; - case TEXCO_REFL: - case TEXCO_GLOB: - case TEXCO_TANGENT: - case TEXCO_STRESS: - case TEXCO_LAVECTOR: - case TEXCO_OBJECT: - case TEXCO_OSA: - case TEXCO_PARTICLE_OR_STRAND: - case TEXCO_SPEED: - case TEXCO_STICKY: - case TEXCO_VIEW: - case TEXCO_WINDOW: - return false; - default: - throw new IllegalStateException("Unknown texture coordinates value: " + texco); - } - } + if (inputData != null) {// make calculations + Vector3f min = bb.getMin(null); + float[] uvCoordsResults = new float[4];// used for coordinates swapping + float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 }; + for (int i = 0; i < ext.length; ++i) { + if (ext[i] == 0) { + ext[i] = 1; + } + } + // now transform the coordinates so that they are in the range of + // <0; 1> + for (int i = 0; i < inputData.length; i += 3) { + uvCoordsResults[1] = (inputData[i] - min.x) / ext[0]; + uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1]; + uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2]; + result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]])); + } + } + return result; + } - /** - * This method returns the bounding box of the given geometries. - * - * @param geometries - * the list of geometries - * @return bounding box of the given geometries - */ - public static BoundingBox getBoundingBox(List geometries) { - BoundingBox result = null; - for (Geometry geometry : geometries) { - BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh()); - if (result == null) { - result = bb; - } else { - result.merge(bb); - } - } - return result; - } + /** + * This method should be used to determine if the texture will ever be + * computed. If the texture coordinates are not supported then the try of + * flattening the texture might result in runtime exceptions occurence. + * + * @param texco + * the texture coordinates type + * @return true if the type is supported and false otherwise + */ + public static boolean isTextureCoordinateTypeSupported(UVCoordinatesType texco) { + switch (texco) { + case TEXCO_ORCO: + case TEXCO_UV: + case TEXCO_NORM: + return true; + case TEXCO_REFL: + case TEXCO_GLOB: + case TEXCO_TANGENT: + case TEXCO_STRESS: + case TEXCO_LAVECTOR: + case TEXCO_OBJECT: + case TEXCO_OSA: + case TEXCO_PARTICLE_OR_STRAND: + case TEXCO_SPEED: + case TEXCO_STICKY: + case TEXCO_VIEW: + case TEXCO_WINDOW: + return false; + default: + throw new IllegalStateException("Unknown texture coordinates value: " + texco); + } + } - /** - * This method returns the bounding box of the given mesh. - * - * @param mesh - * the mesh - * @return bounding box of the given mesh - */ - /* package */static BoundingBox getBoundingBox(Mesh mesh) { - mesh.updateBound(); - BoundingVolume bv = mesh.getBound(); - if (bv instanceof BoundingBox) { - return (BoundingBox) bv; - } else if (bv instanceof BoundingSphere) { - BoundingSphere bs = (BoundingSphere) bv; - float r = bs.getRadius(); - return new BoundingBox(bs.getCenter(), r, r, r); - } else { - throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); - } - } + /** + * This method returns the bounding box of the given geometries. + * + * @param geometries + * the list of geometries + * @return bounding box of the given geometries + */ + public static BoundingBox getBoundingBox(List geometries) { + BoundingBox result = null; + for (Geometry geometry : geometries) { + BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry.getMesh()); + if (result == null) { + result = bb; + } else { + result.merge(bb); + } + } + return result; + } - /** - * This method returns the bounding sphere of the given geometries. - * - * @param geometries - * the list of geometries - * @return bounding sphere of the given geometries - */ - /* package */static BoundingSphere getBoundingSphere(List geometries) { - BoundingSphere result = null; - for (Geometry geometry : geometries) { - BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometry.getMesh()); - if (result == null) { - result = bs; - } else { - result.merge(bs); - } - } - return result; - } + /** + * This method returns the bounding box of the given mesh. + * + * @param mesh + * the mesh + * @return bounding box of the given mesh + */ + /* package */static BoundingBox getBoundingBox(Mesh mesh) { + mesh.updateBound(); + BoundingVolume bv = mesh.getBound(); + if (bv instanceof BoundingBox) { + return (BoundingBox) bv; + } else if (bv instanceof BoundingSphere) { + BoundingSphere bs = (BoundingSphere) bv; + float r = bs.getRadius(); + return new BoundingBox(bs.getCenter(), r, r, r); + } else { + throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); + } + } - /** - * This method returns the bounding sphere of the given mesh. - * - * @param mesh - * the mesh - * @return bounding sphere of the given mesh - */ - /* package */static BoundingSphere getBoundingSphere(Mesh mesh) { - mesh.updateBound(); - BoundingVolume bv = mesh.getBound(); - if (bv instanceof BoundingBox) { - BoundingBox bb = (BoundingBox) bv; - float r = Math.max(bb.getXExtent(), bb.getYExtent()); - r = Math.max(r, bb.getZExtent()); - return new BoundingSphere(r, bb.getCenter()); - } else if (bv instanceof BoundingSphere) { - return (BoundingSphere) bv; - } else { - throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); - } - } + /** + * This method returns the bounding sphere of the given geometries. + * + * @param geometries + * the list of geometries + * @return bounding sphere of the given geometries + */ + /* package */static BoundingSphere getBoundingSphere(List geometries) { + BoundingSphere result = null; + for (Geometry geometry : geometries) { + BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometry.getMesh()); + if (result == null) { + result = bs; + } else { + result.merge(bs); + } + } + return result; + } - /** - * This method returns the bounding tube of the given mesh. - * - * @param mesh - * the mesh - * @return bounding tube of the given mesh - */ - /* package */static BoundingTube getBoundingTube(Mesh mesh) { - Vector3f center = new Vector3f(); - float maxx = -Float.MAX_VALUE, minx = Float.MAX_VALUE; - float maxy = -Float.MAX_VALUE, miny = Float.MAX_VALUE; - float maxz = -Float.MAX_VALUE, minz = Float.MAX_VALUE; + /** + * This method returns the bounding sphere of the given mesh. + * + * @param mesh + * the mesh + * @return bounding sphere of the given mesh + */ + /* package */static BoundingSphere getBoundingSphere(Mesh mesh) { + mesh.updateBound(); + BoundingVolume bv = mesh.getBound(); + if (bv instanceof BoundingBox) { + BoundingBox bb = (BoundingBox) bv; + float r = Math.max(bb.getXExtent(), bb.getYExtent()); + r = Math.max(r, bb.getZExtent()); + return new BoundingSphere(r, bb.getCenter()); + } else if (bv instanceof BoundingSphere) { + return (BoundingSphere) bv; + } else { + throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); + } + } - FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position); - int limit = positions.limit(); - for (int i = 0; i < limit; i += 3) { - float x = positions.get(i); - float y = positions.get(i + 1); - float z = positions.get(i + 2); - center.addLocal(x, y, z); - maxx = x > maxx ? x : maxx; - minx = x < minx ? x : minx; - maxy = y > maxy ? y : maxy; - miny = y < miny ? y : miny; - maxz = z > maxz ? z : maxz; - minz = z < minz ? z : minz; - } - center.divideLocal(limit / 3); + /** + * This method returns the bounding tube of the given mesh. + * + * @param mesh + * the mesh + * @return bounding tube of the given mesh + */ + /* package */static BoundingTube getBoundingTube(Mesh mesh) { + Vector3f center = new Vector3f(); + float maxx = -Float.MAX_VALUE, minx = Float.MAX_VALUE; + float maxy = -Float.MAX_VALUE, miny = Float.MAX_VALUE; + float maxz = -Float.MAX_VALUE, minz = Float.MAX_VALUE; - float radius = Math.max(maxx - minx, maxy - miny) * 0.5f; - return new BoundingTube(radius, maxz - minz, center); - } + FloatBuffer positions = mesh.getFloatBuffer(VertexBuffer.Type.Position); + int limit = positions.limit(); + for (int i = 0; i < limit; i += 3) { + float x = positions.get(i); + float y = positions.get(i + 1); + float z = positions.get(i + 2); + center.addLocal(x, y, z); + maxx = x > maxx ? x : maxx; + minx = x < minx ? x : minx; + maxy = y > maxy ? y : maxy; + miny = y < miny ? y : miny; + maxz = z > maxz ? z : maxz; + minz = z < minz ? z : minz; + } + center.divideLocal(limit / 3); - /** - * This method returns the bounding tube of the given geometries. - * - * @param geometries - * the list of geometries - * @return bounding tube of the given geometries - */ - /* package */static BoundingTube getBoundingTube(List geometries) { - BoundingTube result = null; - for (Geometry geometry : geometries) { - BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometry.getMesh()); - if (result == null) { - result = bt; - } else { - result.merge(bt); - } - } - return result; - } + float radius = Math.max(maxx - minx, maxy - miny) * 0.5f; + return new BoundingTube(radius, maxz - minz, center); + } - /** - * A very simple bounding tube. Id holds only the basic data bout the - * bounding tube and does not provide full functionality of a - * BoundingVolume. Should be replaced with a bounding tube that extends the - * BoundingVolume if it is ever created. - * - * @author Marcin Roguski (Kaelthas) - */ - /* package */static class BoundingTube { - private float radius; - private float height; - private Vector3f center; + /** + * This method returns the bounding tube of the given geometries. + * + * @param geometries + * the list of geometries + * @return bounding tube of the given geometries + */ + /* package */static BoundingTube getBoundingTube(List geometries) { + BoundingTube result = null; + for (Geometry geometry : geometries) { + BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometry.getMesh()); + if (result == null) { + result = bt; + } else { + result.merge(bt); + } + } + return result; + } - /** - * Constructor creates the tube with the given params. - * - * @param radius - * the radius of the tube - * @param height - * the height of the tube - * @param center - * the center of the tube - */ - public BoundingTube(float radius, float height, Vector3f center) { - this.radius = radius; - this.height = height; - this.center = center; - } + /** + * A very simple bounding tube. Id holds only the basic data bout the + * bounding tube and does not provide full functionality of a + * BoundingVolume. Should be replaced with a bounding tube that extends the + * BoundingVolume if it is ever created. + * + * @author Marcin Roguski (Kaelthas) + */ + /* package */static class BoundingTube { + private float radius; + private float height; + private Vector3f center; - /** - * This method merges two bounding tubes. - * - * @param boundingTube - * bounding tube to be merged woth the current one - * @return new instance of bounding tube representing the tubes' merge - */ - public BoundingTube merge(BoundingTube boundingTube) { - // get tubes (tube1.radius >= tube2.radius) - BoundingTube tube1, tube2; - if (this.radius >= boundingTube.radius) { - tube1 = this; - tube2 = boundingTube; - } else { - tube1 = boundingTube; - tube2 = this; - } - float r1 = tube1.radius; - float r2 = tube2.radius; + /** + * Constructor creates the tube with the given params. + * + * @param radius + * the radius of the tube + * @param height + * the height of the tube + * @param center + * the center of the tube + */ + public BoundingTube(float radius, float height, Vector3f center) { + this.radius = radius; + this.height = height; + this.center = center; + } - float minZ = Math.min(tube1.center.z - tube1.height * 0.5f, tube2.center.z - tube2.height * 0.5f); - float maxZ = Math.max(tube1.center.z + tube1.height * 0.5f, tube2.center.z + tube2.height * 0.5f); - float height = maxZ - minZ; - Vector3f distance = tube2.center.subtract(tube1.center); - Vector3f center = tube1.center.add(distance.mult(0.5f)); - distance.z = 0;// projecting this vector on XY plane - float d = distance.length(); - // d <= r1 - r2: tube2 is inside tube1 or touches tube1 from the - // inside - // d > r1 - r2: tube2 is outside or touches tube1 or crosses tube1 - float radius = d <= r1 - r2 ? tube1.radius : (d + r1 + r2) * 0.5f; - return new BoundingTube(radius, height, center); - } + /** + * This method merges two bounding tubes. + * + * @param boundingTube + * bounding tube to be merged woth the current one + * @return new instance of bounding tube representing the tubes' merge + */ + public BoundingTube merge(BoundingTube boundingTube) { + // get tubes (tube1.radius >= tube2.radius) + BoundingTube tube1, tube2; + if (this.radius >= boundingTube.radius) { + tube1 = this; + tube2 = boundingTube; + } else { + tube1 = boundingTube; + tube2 = this; + } + float r1 = tube1.radius; + float r2 = tube2.radius; - /** - * @return the radius of the tube - */ - public float getRadius() { - return radius; - } + float minZ = Math.min(tube1.center.z - tube1.height * 0.5f, tube2.center.z - tube2.height * 0.5f); + float maxZ = Math.max(tube1.center.z + tube1.height * 0.5f, tube2.center.z + tube2.height * 0.5f); + float height = maxZ - minZ; + Vector3f distance = tube2.center.subtract(tube1.center); + Vector3f center = tube1.center.add(distance.mult(0.5f)); + distance.z = 0;// projecting this vector on XY plane + float d = distance.length(); + // d <= r1 - r2: tube2 is inside tube1 or touches tube1 from the + // inside + // d > r1 - r2: tube2 is outside or touches tube1 or crosses tube1 + float radius = d <= r1 - r2 ? tube1.radius : (d + r1 + r2) * 0.5f; + return new BoundingTube(radius, height, center); + } - /** - * @return the height of the tube - */ - public float getHeight() { - return height; - } + /** + * @return the radius of the tube + */ + public float getRadius() { + return radius; + } - /** - * @return the center of the tube - */ - public Vector3f getCenter() { - return center; - } - } + /** + * @return the height of the tube + */ + public float getHeight() { + return height; + } + + /** + * @return the center of the tube + */ + public Vector3f getCenter() { + return center; + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java index ddab4a117..a213bc626 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java @@ -13,235 +13,228 @@ import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.BoundingTu * @author Marcin Roguski (Kaelthas) */ /* package */class UVProjectionGenerator { - /** - * 2D texture mapping (projection) - * @author Marcin Roguski (Kaelthas) - */ - public static enum UVProjectionType { - PROJECTION_FLAT(0), - PROJECTION_CUBE(1), - PROJECTION_TUBE(2), - PROJECTION_SPHERE(3); - - public final int blenderValue; - - private UVProjectionType(int blenderValue) { - this.blenderValue = blenderValue; - } - - public static UVProjectionType valueOf(int blenderValue) { - for(UVProjectionType projectionType : UVProjectionType.values()) { - if(projectionType.blenderValue == blenderValue) { - return projectionType; - } - } - return null; - } - } - - /** - * Flat projection for 2D textures. - * - * @param mesh - * mesh that is to be projected - * @param bb - * the bounding box for projecting - * @return UV coordinates after the projection - */ - public static float[] flatProjection(float[] positions, BoundingBox bb) { - Vector3f min = bb.getMin(null); - float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getZExtent() * 2.0f }; - float[] uvCoordinates = new float[positions.length / 3 * 2]; - for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { - uvCoordinates[j] = (positions[i] - min.x) / ext[0]; - // skip the Y-coordinate - uvCoordinates[j + 1] = (positions[i + 2] - min.z) / ext[1]; - } - return uvCoordinates; - } - - /** - * Cube projection for 2D textures. - * - * @param positions - * points to be projected - * @param bb - * the bounding box for projecting - * @return UV coordinates after the projection - */ - public static float[] cubeProjection(float[] positions, BoundingBox bb) { - Triangle triangle = new Triangle(); - Vector3f x = new Vector3f(1, 0, 0); - Vector3f y = new Vector3f(0, 1, 0); - Vector3f z = new Vector3f(0, 0, 1); - Vector3f min = bb.getMin(null); - float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f, bb.getZExtent() * 2.0f }; - - float[] uvCoordinates = new float[positions.length/3*2]; - float borderAngle = (float) Math.sqrt(2.0f) / 2.0f; - for (int i = 0, pointIndex = 0; i < positions.length; i+=9) { - triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); - triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); - triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); - Vector3f n = triangle.getNormal(); - float dotNX = Math.abs(n.dot(x)); - float dorNY = Math.abs(n.dot(y)); - float dotNZ = Math.abs(n.dot(z)); - if (dotNX > borderAngle) { - if (dotNZ < borderAngle) {// discard X-coordinate - uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2]; - uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2]; - uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2]; - } else {// discard Z-coordinate - uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; - } - } else { - if (dorNY > borderAngle) {// discard Y-coordinate - uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2]; - uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2]; - uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2]; - } else {// discard Z-coordinate - uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; - } - } - triangle.setNormal(null);// clear the previous normal vector - } - return uvCoordinates; - } - - /** - * Tube projection for 2D textures. - * - * @param positions - * points to be projected - * @param bt - * the bounding tube for projecting - * @return UV coordinates after the projection - */ - public static float[] tubeProjection(float[] positions, BoundingTube bt) { - float[] uvCoordinates = new float[positions.length / 3 * 2]; - Vector3f v = new Vector3f(); - float cx = bt.getCenter().x, cz = bt.getCenter().z; - Vector3f uBase = new Vector3f(0, 0, -1); - - float vBase = bt.getCenter().y - bt.getHeight() * 0.5f; - for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { - // calculating U - v.set(positions[i]-cx, 0, positions[i + 2]-cz); - v.normalizeLocal(); - float angle = v.angleBetween(uBase);// result between [0; PI] - if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then - angle = FastMath.TWO_PI - angle; - } - uvCoordinates[j] = angle / FastMath.TWO_PI; - - // calculating V - float y = positions[i + 1]; - uvCoordinates[j + 1] = (y - vBase) / bt.getHeight(); - } - - //looking for splitted triangles - Triangle triangle = new Triangle(); - for (int i = 0; i < positions.length; i+=9) { - triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); - triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); - triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); - float sgn1 = Math.signum(triangle.get1().x-cx); - float sgn2 = Math.signum(triangle.get2().x-cx); - float sgn3 = Math.signum(triangle.get3().x-cx); - float xSideFactor = sgn1 + sgn2 + sgn3; - float ySideFactor = Math.signum(triangle.get1().z-cz)+ - Math.signum(triangle.get2().z-cz)+ - Math.signum(triangle.get3().z-cz); - if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane - if(sgn1==1.0f) { - uvCoordinates[i/3*2] += 1.0f; - } - if(sgn2==1.0f) { - uvCoordinates[(i/3+1)*2] += 1.0f; - } - if(sgn3==1.0f) { - uvCoordinates[(i/3+2)*2] += 1.0f; - } - } - } - return uvCoordinates; - } - - /** - * Sphere projection for 2D textures. - * - * @param positions - * points to be projected - * @param bb - * the bounding box for projecting - * @return UV coordinates after the projection - */ - public static float[] sphereProjection(float[] positions, BoundingSphere bs) {//TODO: rotate it to be vertical - float[] uvCoordinates = new float[positions.length / 3 * 2]; - Vector3f v = new Vector3f(); - float cx = bs.getCenter().x, cy = bs.getCenter().y, cz = bs.getCenter().z; - Vector3f uBase = new Vector3f(0, -1, 0); - Vector3f vBase = new Vector3f(0, 0, -1); - - for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { - // calculating U - v.set(positions[i]-cx, positions[i + 1] - cy, 0); - v.normalizeLocal(); - float angle = v.angleBetween(uBase);// result between [0; PI] - if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then - angle = FastMath.TWO_PI - angle; - } - uvCoordinates[j] = angle / FastMath.TWO_PI; - - // calculating V - v.set(positions[i]-cx, positions[i + 1]-cy, positions[i + 2]-cz); - v.normalizeLocal(); - angle = v.angleBetween(vBase);// result between [0; PI] - uvCoordinates[j+1] = angle / FastMath.PI; - } - - //looking for splitted triangles - Triangle triangle = new Triangle(); - for (int i = 0; i < positions.length; i+=9) { - triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); - triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); - triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); - float sgn1 = Math.signum(triangle.get1().x-cx); - float sgn2 = Math.signum(triangle.get2().x-cx); - float sgn3 = Math.signum(triangle.get3().x-cx); - float xSideFactor = sgn1 + sgn2 + sgn3; - float ySideFactor = Math.signum(triangle.get1().y-cy)+ - Math.signum(triangle.get2().y-cy)+ - Math.signum(triangle.get3().y-cy); - if((xSideFactor>-3 || xSideFactor<3) && ySideFactor<0) {//the triangle is on the splitting plane - if(sgn1==1.0f) { - uvCoordinates[i/3*2] += 1.0f; - } - if(sgn2==1.0f) { - uvCoordinates[(i/3+1)*2] += 1.0f; - } - if(sgn3==1.0f) { - uvCoordinates[(i/3+2)*2] += 1.0f; - } - } - } - return uvCoordinates; - } + /** + * 2D texture mapping (projection) + * @author Marcin Roguski (Kaelthas) + */ + public static enum UVProjectionType { + PROJECTION_FLAT(0), PROJECTION_CUBE(1), PROJECTION_TUBE(2), PROJECTION_SPHERE(3); + + public final int blenderValue; + + private UVProjectionType(int blenderValue) { + this.blenderValue = blenderValue; + } + + public static UVProjectionType valueOf(int blenderValue) { + for (UVProjectionType projectionType : UVProjectionType.values()) { + if (projectionType.blenderValue == blenderValue) { + return projectionType; + } + } + return null; + } + } + + /** + * Flat projection for 2D textures. + * + * @param mesh + * mesh that is to be projected + * @param bb + * the bounding box for projecting + * @return UV coordinates after the projection + */ + public static float[] flatProjection(float[] positions, BoundingBox bb) { + Vector3f min = bb.getMin(null); + float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getZExtent() * 2.0f }; + float[] uvCoordinates = new float[positions.length / 3 * 2]; + for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { + uvCoordinates[j] = (positions[i] - min.x) / ext[0]; + // skip the Y-coordinate + uvCoordinates[j + 1] = (positions[i + 2] - min.z) / ext[1]; + } + return uvCoordinates; + } + + /** + * Cube projection for 2D textures. + * + * @param positions + * points to be projected + * @param bb + * the bounding box for projecting + * @return UV coordinates after the projection + */ + public static float[] cubeProjection(float[] positions, BoundingBox bb) { + Triangle triangle = new Triangle(); + Vector3f x = new Vector3f(1, 0, 0); + Vector3f y = new Vector3f(0, 1, 0); + Vector3f z = new Vector3f(0, 0, 1); + Vector3f min = bb.getMin(null); + float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f, bb.getZExtent() * 2.0f }; + + float[] uvCoordinates = new float[positions.length / 3 * 2]; + float borderAngle = (float) Math.sqrt(2.0f) / 2.0f; + for (int i = 0, pointIndex = 0; i < positions.length; i += 9) { + triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); + triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); + triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); + Vector3f n = triangle.getNormal(); + float dotNX = Math.abs(n.dot(x)); + float dorNY = Math.abs(n.dot(y)); + float dotNZ = Math.abs(n.dot(z)); + if (dotNX > borderAngle) { + if (dotNZ < borderAngle) {// discard X-coordinate + uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2]; + uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2]; + uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2]; + } else {// discard Z-coordinate + uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; + } + } else { + if (dorNY > borderAngle) {// discard Y-coordinate + uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2]; + uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2]; + uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2]; + } else {// discard Z-coordinate + uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; + uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; + uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; + } + } + triangle.setNormal(null);// clear the previous normal vector + } + return uvCoordinates; + } + + /** + * Tube projection for 2D textures. + * + * @param positions + * points to be projected + * @param bt + * the bounding tube for projecting + * @return UV coordinates after the projection + */ + public static float[] tubeProjection(float[] positions, BoundingTube bt) { + float[] uvCoordinates = new float[positions.length / 3 * 2]; + Vector3f v = new Vector3f(); + float cx = bt.getCenter().x, cz = bt.getCenter().z; + Vector3f uBase = new Vector3f(0, 0, -1); + + float vBase = bt.getCenter().y - bt.getHeight() * 0.5f; + for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { + // calculating U + v.set(positions[i] - cx, 0, positions[i + 2] - cz); + v.normalizeLocal(); + float angle = v.angleBetween(uBase);// result between [0; PI] + if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then + angle = FastMath.TWO_PI - angle; + } + uvCoordinates[j] = angle / FastMath.TWO_PI; + + // calculating V + float y = positions[i + 1]; + uvCoordinates[j + 1] = (y - vBase) / bt.getHeight(); + } + + // looking for splitted triangles + Triangle triangle = new Triangle(); + for (int i = 0; i < positions.length; i += 9) { + triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); + triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); + triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); + float sgn1 = Math.signum(triangle.get1().x - cx); + float sgn2 = Math.signum(triangle.get2().x - cx); + float sgn3 = Math.signum(triangle.get3().x - cx); + float xSideFactor = sgn1 + sgn2 + sgn3; + float ySideFactor = Math.signum(triangle.get1().z - cz) + Math.signum(triangle.get2().z - cz) + Math.signum(triangle.get3().z - cz); + if ((xSideFactor > -3 || xSideFactor < 3) && ySideFactor < 0) {// the triangle is on the splitting plane + if (sgn1 == 1.0f) { + uvCoordinates[i / 3 * 2] += 1.0f; + } + if (sgn2 == 1.0f) { + uvCoordinates[(i / 3 + 1) * 2] += 1.0f; + } + if (sgn3 == 1.0f) { + uvCoordinates[(i / 3 + 2) * 2] += 1.0f; + } + } + } + return uvCoordinates; + } + + /** + * Sphere projection for 2D textures. + * + * @param positions + * points to be projected + * @param bb + * the bounding box for projecting + * @return UV coordinates after the projection + */ + public static float[] sphereProjection(float[] positions, BoundingSphere bs) {// TODO: rotate it to be vertical + float[] uvCoordinates = new float[positions.length / 3 * 2]; + Vector3f v = new Vector3f(); + float cx = bs.getCenter().x, cy = bs.getCenter().y, cz = bs.getCenter().z; + Vector3f uBase = new Vector3f(0, -1, 0); + Vector3f vBase = new Vector3f(0, 0, -1); + + for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { + // calculating U + v.set(positions[i] - cx, positions[i + 1] - cy, 0); + v.normalizeLocal(); + float angle = v.angleBetween(uBase);// result between [0; PI] + if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then + angle = FastMath.TWO_PI - angle; + } + uvCoordinates[j] = angle / FastMath.TWO_PI; + + // calculating V + v.set(positions[i] - cx, positions[i + 1] - cy, positions[i + 2] - cz); + v.normalizeLocal(); + angle = v.angleBetween(vBase);// result between [0; PI] + uvCoordinates[j + 1] = angle / FastMath.PI; + } + + // looking for splitted triangles + Triangle triangle = new Triangle(); + for (int i = 0; i < positions.length; i += 9) { + triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); + triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); + triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); + float sgn1 = Math.signum(triangle.get1().x - cx); + float sgn2 = Math.signum(triangle.get2().x - cx); + float sgn3 = Math.signum(triangle.get3().x - cx); + float xSideFactor = sgn1 + sgn2 + sgn3; + float ySideFactor = Math.signum(triangle.get1().y - cy) + Math.signum(triangle.get2().y - cy) + Math.signum(triangle.get3().y - cy); + if ((xSideFactor > -3 || xSideFactor < 3) && ySideFactor < 0) {// the triangle is on the splitting plane + if (sgn1 == 1.0f) { + uvCoordinates[i / 3 * 2] += 1.0f; + } + if (sgn2 == 1.0f) { + uvCoordinates[(i / 3 + 1) * 2] += 1.0f; + } + if (sgn3 == 1.0f) { + uvCoordinates[(i / 3 + 2) * 2] += 1.0f; + } + } + } + return uvCoordinates; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java index 5fa8831a0..5c3504771 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java @@ -13,124 +13,124 @@ import jme3tools.converters.MipMapGenerator; * @author Marcin Roguski (Kaelthas) */ /* package */abstract class AbstractTextureBlender implements TextureBlender { - private static final Logger LOGGER = Logger.getLogger(AbstractTextureBlender.class.getName()); - - protected int flag; - protected boolean negateTexture; - protected int blendType; - protected float[] materialColor; - protected float[] color; - protected float blendFactor; - - public AbstractTextureBlender(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { - this.flag = flag; - this.negateTexture = negateTexture; - this.blendType = blendType; - this.materialColor = materialColor; - this.color = color; - this.blendFactor = blendFactor; - } + private static final Logger LOGGER = Logger.getLogger(AbstractTextureBlender.class.getName()); - /** - * The method that performs the ramp blending. - * - * @param type - * the blend type - * @param materialRGB - * the rgb value of the material, here the result is stored too - * @param fac - * color affection factor - * @param pixelColor - * the texture color - * @param blenderContext - * the blender context - */ - protected void blendHSV(int type, float[] materialRGB, float fac, float[] pixelColor, BlenderContext blenderContext) { - float oneMinusFactor = 1.0f - fac; - MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + protected int flag; + protected boolean negateTexture; + protected int blendType; + protected float[] materialColor; + protected float[] color; + protected float blendFactor; - switch (type) { - case MTEX_BLEND_HUE: {// FIXME: not working well for image textures (works fine for generated textures) - float[] colorTransformResult = new float[3]; - materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); - if (colorTransformResult[0] != 0.0f) { - float colH = colorTransformResult[0]; - materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult); - materialHelper.hsvToRgb(colH, colorTransformResult[1], colorTransformResult[2], colorTransformResult); - materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * colorTransformResult[0]; - materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * colorTransformResult[1]; - materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * colorTransformResult[2]; - } - break; - } - case MTEX_BLEND_SAT: { - float[] colorTransformResult = new float[3]; - materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult); - float h = colorTransformResult[0]; - float s = colorTransformResult[1]; - float v = colorTransformResult[2]; - if (s != 0.0f) { - materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); - materialHelper.hsvToRgb(h, oneMinusFactor * s + fac * colorTransformResult[1], v, materialRGB); - } - break; - } - case MTEX_BLEND_VAL: { - float[] rgbToHsv = new float[3]; - float[] colToHsv = new float[3]; - materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv); - materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); - materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], oneMinusFactor * rgbToHsv[2] + fac * colToHsv[2], materialRGB); - break; - } - case MTEX_BLEND_COLOR: {// FIXME: not working well for image textures (works fine for generated textures) - float[] rgbToHsv = new float[3]; - float[] colToHsv = new float[3]; - materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); - if (colToHsv[2] != 0) { - materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv); - materialHelper.hsvToRgb(colToHsv[0], colToHsv[1], rgbToHsv[2], rgbToHsv); - materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * rgbToHsv[0]; - materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * rgbToHsv[1]; - materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * rgbToHsv[2]; - } - break; - } - default: - throw new IllegalStateException("Unknown ramp type: " + type); - } - } - - public void copyBlendingData(TextureBlender textureBlender) { - if(textureBlender instanceof AbstractTextureBlender) { - this.flag = ((AbstractTextureBlender) textureBlender).flag; - this.negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture; - this.blendType = ((AbstractTextureBlender) textureBlender).blendType; - this.materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone(); - this.color = ((AbstractTextureBlender) textureBlender).color.clone(); - this.blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor; - } else { - LOGGER.warning("Cannot copy blending data from other types than " + this.getClass()); - } - } - - /** - * The method prepares images for blending. It generates mipmaps if one of - * the images has them defined and the other one has not. - * - * @param target - * the image where the blending result is stored - * @param source - * the image that is being read only - */ - protected void prepareImagesForBlending(Image target, Image source) { - LOGGER.fine("Generating mipmaps if needed!"); - boolean targetHasMipmaps = target == null ? false : target.getMipMapSizes() != null && target.getMipMapSizes().length > 0; - boolean sourceHasMipmaps = source == null ? false : source.getMipMapSizes() != null && source.getMipMapSizes().length > 0; - if (target != null && !targetHasMipmaps && sourceHasMipmaps) { - MipMapGenerator.generateMipMaps(target); - } else if (source != null && !sourceHasMipmaps && targetHasMipmaps) { - MipMapGenerator.generateMipMaps(source); - } - } + public AbstractTextureBlender(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { + this.flag = flag; + this.negateTexture = negateTexture; + this.blendType = blendType; + this.materialColor = materialColor; + this.color = color; + this.blendFactor = blendFactor; + } + + /** + * The method that performs the ramp blending. + * + * @param type + * the blend type + * @param materialRGB + * the rgb value of the material, here the result is stored too + * @param fac + * color affection factor + * @param pixelColor + * the texture color + * @param blenderContext + * the blender context + */ + protected void blendHSV(int type, float[] materialRGB, float fac, float[] pixelColor, BlenderContext blenderContext) { + float oneMinusFactor = 1.0f - fac; + MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + + switch (type) { + case MTEX_BLEND_HUE: {// FIXME: not working well for image textures (works fine for generated textures) + float[] colorTransformResult = new float[3]; + materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); + if (colorTransformResult[0] != 0.0f) { + float colH = colorTransformResult[0]; + materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult); + materialHelper.hsvToRgb(colH, colorTransformResult[1], colorTransformResult[2], colorTransformResult); + materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * colorTransformResult[0]; + materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * colorTransformResult[1]; + materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * colorTransformResult[2]; + } + break; + } + case MTEX_BLEND_SAT: { + float[] colorTransformResult = new float[3]; + materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult); + float h = colorTransformResult[0]; + float s = colorTransformResult[1]; + float v = colorTransformResult[2]; + if (s != 0.0f) { + materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); + materialHelper.hsvToRgb(h, oneMinusFactor * s + fac * colorTransformResult[1], v, materialRGB); + } + break; + } + case MTEX_BLEND_VAL: { + float[] rgbToHsv = new float[3]; + float[] colToHsv = new float[3]; + materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv); + materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); + materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], oneMinusFactor * rgbToHsv[2] + fac * colToHsv[2], materialRGB); + break; + } + case MTEX_BLEND_COLOR: {// FIXME: not working well for image textures (works fine for generated textures) + float[] rgbToHsv = new float[3]; + float[] colToHsv = new float[3]; + materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); + if (colToHsv[2] != 0) { + materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv); + materialHelper.hsvToRgb(colToHsv[0], colToHsv[1], rgbToHsv[2], rgbToHsv); + materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * rgbToHsv[0]; + materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * rgbToHsv[1]; + materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * rgbToHsv[2]; + } + break; + } + default: + throw new IllegalStateException("Unknown ramp type: " + type); + } + } + + public void copyBlendingData(TextureBlender textureBlender) { + if (textureBlender instanceof AbstractTextureBlender) { + this.flag = ((AbstractTextureBlender) textureBlender).flag; + this.negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture; + this.blendType = ((AbstractTextureBlender) textureBlender).blendType; + this.materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone(); + this.color = ((AbstractTextureBlender) textureBlender).color.clone(); + this.blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor; + } else { + LOGGER.warning("Cannot copy blending data from other types than " + this.getClass()); + } + } + + /** + * The method prepares images for blending. It generates mipmaps if one of + * the images has them defined and the other one has not. + * + * @param target + * the image where the blending result is stored + * @param source + * the image that is being read only + */ + protected void prepareImagesForBlending(Image target, Image source) { + LOGGER.fine("Generating mipmaps if needed!"); + boolean targetHasMipmaps = target == null ? false : target.getMipMapSizes() != null && target.getMipMapSizes().length > 0; + boolean sourceHasMipmaps = source == null ? false : source.getMipMapSizes() != null && source.getMipMapSizes().length > 0; + if (target != null && !targetHasMipmaps && sourceHasMipmaps) { + MipMapGenerator.generateMipMaps(target); + } else if (source != null && !sourceHasMipmaps && targetHasMipmaps) { + MipMapGenerator.generateMipMaps(source); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java index 30951850f..7ba2b98f2 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java @@ -10,44 +10,44 @@ import com.jme3.texture.Image; * @author Marcin Roguski (Kaelthas) */ public interface TextureBlender { - // types of blending - int MTEX_BLEND = 0; - int MTEX_MUL = 1; - int MTEX_ADD = 2; - int MTEX_SUB = 3; - int MTEX_DIV = 4; - int MTEX_DARK = 5; - int MTEX_DIFF = 6; - int MTEX_LIGHT = 7; - int MTEX_SCREEN = 8; - int MTEX_OVERLAY = 9; - int MTEX_BLEND_HUE = 10; - int MTEX_BLEND_SAT = 11; - int MTEX_BLEND_VAL = 12; - int MTEX_BLEND_COLOR = 13; - int MTEX_NUM_BLENDTYPES = 14; + // types of blending + int MTEX_BLEND = 0; + int MTEX_MUL = 1; + int MTEX_ADD = 2; + int MTEX_SUB = 3; + int MTEX_DIV = 4; + int MTEX_DARK = 5; + int MTEX_DIFF = 6; + int MTEX_LIGHT = 7; + int MTEX_SCREEN = 8; + int MTEX_OVERLAY = 9; + int MTEX_BLEND_HUE = 10; + int MTEX_BLEND_SAT = 11; + int MTEX_BLEND_VAL = 12; + int MTEX_BLEND_COLOR = 13; + int MTEX_NUM_BLENDTYPES = 14; - /** - * This method blends the given texture with material color and the defined - * color in 'map to' panel. As a result of this method a new texture is - * created. The input texture is NOT. - * - * @param image - * the image we use in blending - * @param baseImage - * the texture that is underneath the current texture (its pixels - * will be used instead of material color) - * @param blenderContext - * the blender context - * @return new image that was created after the blending - */ - Image blend(Image image, Image baseImage, BlenderContext blenderContext); + /** + * This method blends the given texture with material color and the defined + * color in 'map to' panel. As a result of this method a new texture is + * created. The input texture is NOT. + * + * @param image + * the image we use in blending + * @param baseImage + * the texture that is underneath the current texture (its pixels + * will be used instead of material color) + * @param blenderContext + * the blender context + * @return new image that was created after the blending + */ + Image blend(Image image, Image baseImage, BlenderContext blenderContext); - /** - * Copies blending data. Used for blending type format changing. - * - * @param textureBlender - * the blend data that should be copied - */ - void copyBlendingData(TextureBlender textureBlender); + /** + * Copies blending data. Used for blending type format changing. + * + * @param textureBlender + * the blend data that should be copied + */ + void copyBlendingData(TextureBlender textureBlender); } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java index c86989502..78789a968 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java @@ -43,202 +43,184 @@ import java.nio.ByteBuffer; import java.util.ArrayList; /** - * The class that is responsible for blending the following texture types: - *
  • RGBA8 - *
  • ABGR8 - *
  • BGR8 - *
  • RGB8 - * Not yet supported (but will be): - *
  • ARGB4444: - *
  • RGB10: - *
  • RGB111110F: - *
  • RGB16: - *
  • RGB16F: - *
  • RGB16F_to_RGB111110F: - *
  • RGB16F_to_RGB9E5: - *
  • RGB32F: - *
  • RGB565: - *
  • RGB5A1: - *
  • RGB9E5: - *
  • RGBA16: - *
  • RGBA16F + * The class that is responsible for blending the following texture types:
  • RGBA8
  • ABGR8
  • BGR8
  • RGB8 Not yet supported (but will be):
  • ARGB4444:
  • RGB10:
  • RGB111110F:
  • RGB16:
  • RGB16F:
  • RGB16F_to_RGB111110F:
  • RGB16F_to_RGB9E5:
  • RGB32F:
  • RGB565:
  • RGB5A1:
  • RGB9E5:
  • RGBA16:
  • RGBA16F * @author Marcin Roguski (Kaelthas) */ public class TextureBlenderAWT extends AbstractTextureBlender { - public TextureBlenderAWT(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { - super(flag, negateTexture, blendType, materialColor, color, blendFactor); - } - - public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { - this.prepareImagesForBlending(image, baseImage); - - float[] pixelColor = new float[] { color[0], color[1], color[2], 1.0f }; - Format format = image.getFormat(); - - PixelInputOutput basePixelIO = null, pixelReader = PixelIOFactory.getPixelIO(format); - TexturePixel basePixel = null, pixel = new TexturePixel(); - float[] materialColor = this.materialColor; - if(baseImage != null) { - basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); - materialColor = new float[this.materialColor.length]; - basePixel = new TexturePixel(); - } - - int width = image.getWidth(); - int height = image.getHeight(); - int depth = image.getDepth(); - if (depth == 0) { - depth = 1; - } - ArrayList dataArray = new ArrayList(depth); - - float[] resultPixel = new float[4]; - for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { - ByteBuffer data = image.getData(dataLayerIndex); - data.rewind(); - ByteBuffer newData = BufferUtils.createByteBuffer(width * height * 4); - - int dataIndex = 0, x = 0, y = 0, index = 0; - while (index < data.limit()) { - //getting the proper material color if the base texture is applied - if(basePixelIO != null) { - basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y); - basePixel.toRGBA(materialColor); - } - - //reading the current texture's pixel - pixelReader.read(image, dataLayerIndex, pixel, index); - index += image.getFormat().getBitsPerPixel() >> 3; - pixel.toRGBA(pixelColor); - if (negateTexture) { - pixel.negate(); - } - - this.blendPixel(resultPixel, materialColor, pixelColor, blenderContext); - newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); - newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); - newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); - newData.put(dataIndex++, (byte) (pixelColor[3] * 255.0f)); - - ++x; - if(x >= width) { - x = 0; - ++y; - } - } - dataArray.add(newData); - } - - Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0)); - if(image.getMipMapSizes() != null) { - result.setMipMapSizes(image.getMipMapSizes().clone()); - } - return result; - } + public TextureBlenderAWT(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { + super(flag, negateTexture, blendType, materialColor, color, blendFactor); + } - /** - * This method blends the single pixel depending on the blending type. - * - * @param result - * the result pixel - * @param materialColor - * the material color - * @param pixelColor - * the pixel color - * @param blenderContext - * the blender context - */ - protected void blendPixel(float[] result, float[] materialColor, float[] pixelColor, BlenderContext blenderContext) { - float blendFactor = this.blendFactor * pixelColor[3]; - float oneMinusFactor = 1.0f - blendFactor, col; + public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { + this.prepareImagesForBlending(image, baseImage); - switch (blendType) { - case MTEX_BLEND: - result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0]; - result[1] = blendFactor * pixelColor[1] + oneMinusFactor * materialColor[1]; - result[2] = blendFactor * pixelColor[2] + oneMinusFactor * materialColor[2]; - break; - case MTEX_MUL: - result[0] = (oneMinusFactor + blendFactor * materialColor[0]) * pixelColor[0]; - result[1] = (oneMinusFactor + blendFactor * materialColor[1]) * pixelColor[1]; - result[2] = (oneMinusFactor + blendFactor * materialColor[2]) * pixelColor[2]; - break; - case MTEX_DIV: - if (pixelColor[0] != 0.0) { - result[0] = (oneMinusFactor * materialColor[0] + blendFactor * materialColor[0] / pixelColor[0]) * 0.5f; - } - if (pixelColor[1] != 0.0) { - result[1] = (oneMinusFactor * materialColor[1] + blendFactor * materialColor[1] / pixelColor[1]) * 0.5f; - } - if (pixelColor[2] != 0.0) { - result[2] = (oneMinusFactor * materialColor[2] + blendFactor * materialColor[2] / pixelColor[2]) * 0.5f; - } - break; - case MTEX_SCREEN: - result[0] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]); - result[1] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]); - result[2] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]); - break; - case MTEX_OVERLAY: - if (materialColor[0] < 0.5f) { - result[0] = pixelColor[0] * (oneMinusFactor + 2.0f * blendFactor * materialColor[0]); - } else { - result[0] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]); - } - if (materialColor[1] < 0.5f) { - result[1] = pixelColor[1] * (oneMinusFactor + 2.0f * blendFactor * materialColor[1]); - } else { - result[1] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]); - } - if (materialColor[2] < 0.5f) { - result[2] = pixelColor[2] * (oneMinusFactor + 2.0f * blendFactor * materialColor[2]); - } else { - result[2] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]); - } - break; - case MTEX_SUB: - result[0] = materialColor[0] - blendFactor * pixelColor[0]; - result[1] = materialColor[1] - blendFactor * pixelColor[1]; - result[2] = materialColor[2] - blendFactor * pixelColor[2]; - result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); - result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); - result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); - break; - case MTEX_ADD: - result[0] = (blendFactor * pixelColor[0] + materialColor[0]) * 0.5f; - result[1] = (blendFactor * pixelColor[1] + materialColor[1]) * 0.5f; - result[2] = (blendFactor * pixelColor[2] + materialColor[2]) * 0.5f; - break; - case MTEX_DIFF: - result[0] = oneMinusFactor * materialColor[0] + blendFactor * Math.abs(materialColor[0] - pixelColor[0]); - result[1] = oneMinusFactor * materialColor[1] + blendFactor * Math.abs(materialColor[1] - pixelColor[1]); - result[2] = oneMinusFactor * materialColor[2] + blendFactor * Math.abs(materialColor[2] - pixelColor[2]); - break; - case MTEX_DARK: - col = blendFactor * pixelColor[0]; - result[0] = col < materialColor[0] ? col : materialColor[0]; - col = blendFactor * pixelColor[1]; - result[1] = col < materialColor[1] ? col : materialColor[1]; - col = blendFactor * pixelColor[2]; - result[2] = col < materialColor[2] ? col : materialColor[2]; - break; - case MTEX_LIGHT: - col = blendFactor * pixelColor[0]; - result[0] = col > materialColor[0] ? col : materialColor[0]; - col = blendFactor * pixelColor[1]; - result[1] = col > materialColor[1] ? col : materialColor[1]; - col = blendFactor * pixelColor[2]; - result[2] = col > materialColor[2] ? col : materialColor[2]; - break; - case MTEX_BLEND_HUE: - case MTEX_BLEND_SAT: - case MTEX_BLEND_VAL: - case MTEX_BLEND_COLOR: - System.arraycopy(materialColor, 0, result, 0, 3); - this.blendHSV(blendType, result, blendFactor, pixelColor, blenderContext); - break; - default: - throw new IllegalStateException("Unknown blend type: " + blendType); - } - } + float[] pixelColor = new float[] { color[0], color[1], color[2], 1.0f }; + Format format = image.getFormat(); + + PixelInputOutput basePixelIO = null, pixelReader = PixelIOFactory.getPixelIO(format); + TexturePixel basePixel = null, pixel = new TexturePixel(); + float[] materialColor = this.materialColor; + if (baseImage != null) { + basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); + materialColor = new float[this.materialColor.length]; + basePixel = new TexturePixel(); + } + + int width = image.getWidth(); + int height = image.getHeight(); + int depth = image.getDepth(); + if (depth == 0) { + depth = 1; + } + ArrayList dataArray = new ArrayList(depth); + + float[] resultPixel = new float[4]; + for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { + ByteBuffer data = image.getData(dataLayerIndex); + data.rewind(); + ByteBuffer newData = BufferUtils.createByteBuffer(width * height * 4); + + int dataIndex = 0, x = 0, y = 0, index = 0; + while (index < data.limit()) { + // getting the proper material color if the base texture is applied + if (basePixelIO != null) { + basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y); + basePixel.toRGBA(materialColor); + } + + // reading the current texture's pixel + pixelReader.read(image, dataLayerIndex, pixel, index); + index += image.getFormat().getBitsPerPixel() >> 3; + pixel.toRGBA(pixelColor); + if (negateTexture) { + pixel.negate(); + } + + this.blendPixel(resultPixel, materialColor, pixelColor, blenderContext); + newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); + newData.put(dataIndex++, (byte) (pixelColor[3] * 255.0f)); + + ++x; + if (x >= width) { + x = 0; + ++y; + } + } + dataArray.add(newData); + } + + Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0)); + if (image.getMipMapSizes() != null) { + result.setMipMapSizes(image.getMipMapSizes().clone()); + } + return result; + } + + /** + * This method blends the single pixel depending on the blending type. + * + * @param result + * the result pixel + * @param materialColor + * the material color + * @param pixelColor + * the pixel color + * @param blenderContext + * the blender context + */ + protected void blendPixel(float[] result, float[] materialColor, float[] pixelColor, BlenderContext blenderContext) { + float blendFactor = this.blendFactor * pixelColor[3]; + float oneMinusFactor = 1.0f - blendFactor, col; + + switch (blendType) { + case MTEX_BLEND: + result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0]; + result[1] = blendFactor * pixelColor[1] + oneMinusFactor * materialColor[1]; + result[2] = blendFactor * pixelColor[2] + oneMinusFactor * materialColor[2]; + break; + case MTEX_MUL: + result[0] = (oneMinusFactor + blendFactor * materialColor[0]) * pixelColor[0]; + result[1] = (oneMinusFactor + blendFactor * materialColor[1]) * pixelColor[1]; + result[2] = (oneMinusFactor + blendFactor * materialColor[2]) * pixelColor[2]; + break; + case MTEX_DIV: + if (pixelColor[0] != 0.0) { + result[0] = (oneMinusFactor * materialColor[0] + blendFactor * materialColor[0] / pixelColor[0]) * 0.5f; + } + if (pixelColor[1] != 0.0) { + result[1] = (oneMinusFactor * materialColor[1] + blendFactor * materialColor[1] / pixelColor[1]) * 0.5f; + } + if (pixelColor[2] != 0.0) { + result[2] = (oneMinusFactor * materialColor[2] + blendFactor * materialColor[2] / pixelColor[2]) * 0.5f; + } + break; + case MTEX_SCREEN: + result[0] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]); + result[1] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]); + result[2] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]); + break; + case MTEX_OVERLAY: + if (materialColor[0] < 0.5f) { + result[0] = pixelColor[0] * (oneMinusFactor + 2.0f * blendFactor * materialColor[0]); + } else { + result[0] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]); + } + if (materialColor[1] < 0.5f) { + result[1] = pixelColor[1] * (oneMinusFactor + 2.0f * blendFactor * materialColor[1]); + } else { + result[1] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]); + } + if (materialColor[2] < 0.5f) { + result[2] = pixelColor[2] * (oneMinusFactor + 2.0f * blendFactor * materialColor[2]); + } else { + result[2] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]); + } + break; + case MTEX_SUB: + result[0] = materialColor[0] - blendFactor * pixelColor[0]; + result[1] = materialColor[1] - blendFactor * pixelColor[1]; + result[2] = materialColor[2] - blendFactor * pixelColor[2]; + result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); + result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); + result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); + break; + case MTEX_ADD: + result[0] = (blendFactor * pixelColor[0] + materialColor[0]) * 0.5f; + result[1] = (blendFactor * pixelColor[1] + materialColor[1]) * 0.5f; + result[2] = (blendFactor * pixelColor[2] + materialColor[2]) * 0.5f; + break; + case MTEX_DIFF: + result[0] = oneMinusFactor * materialColor[0] + blendFactor * Math.abs(materialColor[0] - pixelColor[0]); + result[1] = oneMinusFactor * materialColor[1] + blendFactor * Math.abs(materialColor[1] - pixelColor[1]); + result[2] = oneMinusFactor * materialColor[2] + blendFactor * Math.abs(materialColor[2] - pixelColor[2]); + break; + case MTEX_DARK: + col = blendFactor * pixelColor[0]; + result[0] = col < materialColor[0] ? col : materialColor[0]; + col = blendFactor * pixelColor[1]; + result[1] = col < materialColor[1] ? col : materialColor[1]; + col = blendFactor * pixelColor[2]; + result[2] = col < materialColor[2] ? col : materialColor[2]; + break; + case MTEX_LIGHT: + col = blendFactor * pixelColor[0]; + result[0] = col > materialColor[0] ? col : materialColor[0]; + col = blendFactor * pixelColor[1]; + result[1] = col > materialColor[1] ? col : materialColor[1]; + col = blendFactor * pixelColor[2]; + result[2] = col > materialColor[2] ? col : materialColor[2]; + break; + case MTEX_BLEND_HUE: + case MTEX_BLEND_SAT: + case MTEX_BLEND_VAL: + case MTEX_BLEND_COLOR: + System.arraycopy(materialColor, 0, result, 0, 3); + this.blendHSV(blendType, result, blendFactor, pixelColor, blenderContext); + break; + default: + throw new IllegalStateException("Unknown blend type: " + blendType); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java index 39625f4d0..f6a2e8a91 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java @@ -12,113 +12,109 @@ import java.util.ArrayList; import jme3tools.converters.RGB565; /** - * The class that is responsible for blending the following texture types: - *
  • DXT1 - *
  • DXT1A - *
  • DXT3 - *
  • DXT5 + * The class that is responsible for blending the following texture types:
  • DXT1
  • DXT1A
  • DXT3
  • DXT5 * @author Marcin Roguski (Kaelthas) */ public class TextureBlenderDDS extends TextureBlenderAWT { - public TextureBlenderDDS(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { - super(flag, negateTexture, blendType, materialColor, color, blendFactor); - } - - @Override - public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { - this.prepareImagesForBlending(image, baseImage); - - Format format = image.getFormat(); - int width = image.getWidth(); - int height = image.getHeight(); - int depth = image.getDepth(); - if (depth == 0) { - depth = 1; - } - ArrayList dataArray = new ArrayList(depth); - - PixelInputOutput basePixelIO = null; - float[][] compressedMaterialColor = null; - TexturePixel[] baseTextureColors = null; - if(baseImage != null) { - basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); - compressedMaterialColor = new float[2][4]; - baseTextureColors = new TexturePixel[] { new TexturePixel(), new TexturePixel() }; - } - - float[] resultPixel = new float[4]; - float[] pixelColor = new float[4]; - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel() }; - int baseXTexelIndex = 0, baseYTexelIndex = 0; - float[] alphas = new float[] {1, 1}; - for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { - ByteBuffer data = image.getData(dataLayerIndex); - data.rewind(); - ByteBuffer newData = BufferUtils.createByteBuffer(data.remaining()); - while (data.hasRemaining()) { - if(format == Format.DXT3) { - long alpha = data.getLong(); - //get alpha for first and last pixel that is compressed in the texel - byte alpha0 = (byte)(alpha << 4 & 0xFF); - byte alpha1 = (byte)(alpha >> 60 & 0xFF); - alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; - alphas[1] = alpha1 >= 0 ? alpha1 / 255.0f : 1.0f - ~alpha1 / 255.0f; - newData.putLong(alpha); - } else if(format == Format.DXT5) { - byte alpha0 = data.get(); - byte alpha1 = data.get(); - alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; - alphas[1] = alpha1 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; - newData.put(alpha0); - newData.put(alpha1); - //only read the next 6 bytes (these are alpha indexes) - newData.putInt(data.getInt()); - newData.putShort(data.getShort()); - } - int col0 = RGB565.RGB565_to_ARGB8(data.getShort()); - int col1 = RGB565.RGB565_to_ARGB8(data.getShort()); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - //compressing 16 pixels from the base texture as if they belonged to a texel - if(baseImage != null) { - //reading pixels (first and last of the 16 colors array) - basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[0], baseXTexelIndex << 2, baseYTexelIndex << 2);//first pixel - basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[1], baseXTexelIndex << 2 + 4, baseYTexelIndex << 2 + 4);//last pixel - baseTextureColors[0].toRGBA(compressedMaterialColor[0]); - baseTextureColors[1].toRGBA(compressedMaterialColor[1]); - } + public TextureBlenderDDS(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { + super(flag, negateTexture, blendType, materialColor, color, blendFactor); + } - // blending colors - for (int i = 0; i < colors.length; ++i) { - if (negateTexture) { - colors[i].negate(); - } - colors[i].toRGBA(pixelColor); - pixelColor[3] = alphas[i]; - this.blendPixel(resultPixel, compressedMaterialColor != null ? compressedMaterialColor[i] : materialColor, pixelColor, blenderContext); - colors[i].fromARGB(1, resultPixel[0], resultPixel[1], resultPixel[2]); - int argb8 = colors[i].toARGB8(); - short rgb565 = RGB565.ARGB8_to_RGB565(argb8); - newData.putShort(rgb565); - } + @Override + public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { + this.prepareImagesForBlending(image, baseImage); - // just copy the remaining 4 bytes of the current texel - newData.putInt(data.getInt()); - - ++baseXTexelIndex; - if(baseXTexelIndex > image.getWidth() >> 2) { - baseXTexelIndex = 0; - ++baseYTexelIndex; - } - } - dataArray.add(newData); - } - - Image result = dataArray.size() > 1 ? new Image(format, width, height, depth, dataArray) : new Image(format, width, height, dataArray.get(0)); - if(image.getMipMapSizes() != null) { - result.setMipMapSizes(image.getMipMapSizes().clone()); - } - return result; - } + Format format = image.getFormat(); + int width = image.getWidth(); + int height = image.getHeight(); + int depth = image.getDepth(); + if (depth == 0) { + depth = 1; + } + ArrayList dataArray = new ArrayList(depth); + + PixelInputOutput basePixelIO = null; + float[][] compressedMaterialColor = null; + TexturePixel[] baseTextureColors = null; + if (baseImage != null) { + basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); + compressedMaterialColor = new float[2][4]; + baseTextureColors = new TexturePixel[] { new TexturePixel(), new TexturePixel() }; + } + + float[] resultPixel = new float[4]; + float[] pixelColor = new float[4]; + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel() }; + int baseXTexelIndex = 0, baseYTexelIndex = 0; + float[] alphas = new float[] { 1, 1 }; + for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { + ByteBuffer data = image.getData(dataLayerIndex); + data.rewind(); + ByteBuffer newData = BufferUtils.createByteBuffer(data.remaining()); + while (data.hasRemaining()) { + if (format == Format.DXT3) { + long alpha = data.getLong(); + // get alpha for first and last pixel that is compressed in the texel + byte alpha0 = (byte) (alpha << 4 & 0xFF); + byte alpha1 = (byte) (alpha >> 60 & 0xFF); + alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; + alphas[1] = alpha1 >= 0 ? alpha1 / 255.0f : 1.0f - ~alpha1 / 255.0f; + newData.putLong(alpha); + } else if (format == Format.DXT5) { + byte alpha0 = data.get(); + byte alpha1 = data.get(); + alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; + alphas[1] = alpha1 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; + newData.put(alpha0); + newData.put(alpha1); + // only read the next 6 bytes (these are alpha indexes) + newData.putInt(data.getInt()); + newData.putShort(data.getShort()); + } + int col0 = RGB565.RGB565_to_ARGB8(data.getShort()); + int col1 = RGB565.RGB565_to_ARGB8(data.getShort()); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // compressing 16 pixels from the base texture as if they belonged to a texel + if (baseImage != null) { + // reading pixels (first and last of the 16 colors array) + basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[0], baseXTexelIndex << 2, baseYTexelIndex << 2);// first pixel + basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[1], baseXTexelIndex << 2 + 4, baseYTexelIndex << 2 + 4);// last pixel + baseTextureColors[0].toRGBA(compressedMaterialColor[0]); + baseTextureColors[1].toRGBA(compressedMaterialColor[1]); + } + + // blending colors + for (int i = 0; i < colors.length; ++i) { + if (negateTexture) { + colors[i].negate(); + } + colors[i].toRGBA(pixelColor); + pixelColor[3] = alphas[i]; + this.blendPixel(resultPixel, compressedMaterialColor != null ? compressedMaterialColor[i] : materialColor, pixelColor, blenderContext); + colors[i].fromARGB(1, resultPixel[0], resultPixel[1], resultPixel[2]); + int argb8 = colors[i].toARGB8(); + short rgb565 = RGB565.ARGB8_to_RGB565(argb8); + newData.putShort(rgb565); + } + + // just copy the remaining 4 bytes of the current texel + newData.putInt(data.getInt()); + + ++baseXTexelIndex; + if (baseXTexelIndex > image.getWidth() >> 2) { + baseXTexelIndex = 0; + ++baseYTexelIndex; + } + } + dataArray.add(newData); + } + + Image result = dataArray.size() > 1 ? new Image(format, width, height, depth, dataArray) : new Image(format, width, height, dataArray.get(0)); + if (image.getMipMapSizes() != null) { + result.setMipMapSizes(image.getMipMapSizes().clone()); + } + return result; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java index 631436316..25c13f32a 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java @@ -43,86 +43,86 @@ import java.util.logging.Logger; * @author Marcin Roguski (Kaelthas) */ public class TextureBlenderFactory { - private static final Logger LOGGER = Logger.getLogger(TextureBlenderFactory.class.getName()); + private static final Logger LOGGER = Logger.getLogger(TextureBlenderFactory.class.getName()); - /** - * This method creates the blending class. - * - * @param format - * the texture format - * @return texture blending class - */ - public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) { - switch (format) { - case Luminance8: - case Luminance8Alpha8: - case Luminance16: - case Luminance16Alpha16: - case Luminance16F: - case Luminance16FAlpha16F: - case Luminance32F: - return new TextureBlenderLuminance(flag, negate, blendType, materialColor, color, colfac); - case RGBA8: - case ABGR8: - case BGR8: - case RGB8: - case RGB10: - case RGB111110F: - case RGB16: - case RGB16F: - case RGB16F_to_RGB111110F: - case RGB16F_to_RGB9E5: - case RGB32F: - case RGB565: - case RGB5A1: - case RGB9E5: - case RGBA16: - case RGBA16F: - case RGBA32F: - return new TextureBlenderAWT(flag, negate, blendType, materialColor, color, colfac); - case DXT1: - case DXT1A: - case DXT3: - case DXT5: - return new TextureBlenderDDS(flag, negate, blendType, materialColor, color, colfac); - case Alpha16: - case Alpha8: - case ARGB4444: - case Depth: - case Depth16: - case Depth24: - case Depth32: - case Depth32F: - case Intensity16: - case Intensity8: - case LATC: - case LTC: - LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}. Returning a blender that does not change the texture.", format); - return new TextureBlender() { - public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { - return image; - } + /** + * This method creates the blending class. + * + * @param format + * the texture format + * @return texture blending class + */ + public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) { + switch (format) { + case Luminance8: + case Luminance8Alpha8: + case Luminance16: + case Luminance16Alpha16: + case Luminance16F: + case Luminance16FAlpha16F: + case Luminance32F: + return new TextureBlenderLuminance(flag, negate, blendType, materialColor, color, colfac); + case RGBA8: + case ABGR8: + case BGR8: + case RGB8: + case RGB10: + case RGB111110F: + case RGB16: + case RGB16F: + case RGB16F_to_RGB111110F: + case RGB16F_to_RGB9E5: + case RGB32F: + case RGB565: + case RGB5A1: + case RGB9E5: + case RGBA16: + case RGBA16F: + case RGBA32F: + return new TextureBlenderAWT(flag, negate, blendType, materialColor, color, colfac); + case DXT1: + case DXT1A: + case DXT3: + case DXT5: + return new TextureBlenderDDS(flag, negate, blendType, materialColor, color, colfac); + case Alpha16: + case Alpha8: + case ARGB4444: + case Depth: + case Depth16: + case Depth24: + case Depth32: + case Depth32F: + case Intensity16: + case Intensity8: + case LATC: + case LTC: + LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}. Returning a blender that does not change the texture.", format); + return new TextureBlender() { + public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { + return image; + } - public void copyBlendingData(TextureBlender textureBlender) { - } - }; - default: - throw new IllegalStateException("Unknown image format type: " + format); - } - } + public void copyBlendingData(TextureBlender textureBlender) { + } + }; + default: + throw new IllegalStateException("Unknown image format type: " + format); + } + } - /** - * This method changes the image format in the texture blender. - * - * @param format - * the new image format - * @param textureBlender - * the texture blender that will be altered - * @return altered texture blender - */ - public static TextureBlender alterTextureType(Format format, TextureBlender textureBlender) { - TextureBlender result = TextureBlenderFactory.createTextureBlender(format, 0, false, 0, null, null, 0); - result.copyBlendingData(textureBlender); - return result; - } + /** + * This method changes the image format in the texture blender. + * + * @param format + * the new image format + * @param textureBlender + * the texture blender that will be altered + * @return altered texture blender + */ + public static TextureBlender alterTextureType(Format format, TextureBlender textureBlender) { + TextureBlender result = TextureBlenderFactory.createTextureBlender(format, 0, false, 0, null, null, 0); + result.copyBlendingData(textureBlender); + return result; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java index be1b593a6..2e255d292 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java @@ -14,236 +14,228 @@ import java.util.logging.Level; import java.util.logging.Logger; /** - * The class that is responsible for blending the following texture types: - *
  • Luminance8 - *
  • Luminance8Alpha8 - * Not yet supported (but will be): - *
  • Luminance16: - *
  • Luminance16Alpha16: - *
  • Luminance16F: - *
  • Luminance16FAlpha16F: - *
  • Luminance32F: + * The class that is responsible for blending the following texture types:
  • Luminance8
  • Luminance8Alpha8 Not yet supported (but will be):
  • Luminance16:
  • Luminance16Alpha16:
  • Luminance16F:
  • Luminance16FAlpha16F:
  • Luminance32F: * @author Marcin Roguski (Kaelthas) */ public class TextureBlenderLuminance extends AbstractTextureBlender { - private static final Logger LOGGER = Logger.getLogger(TextureBlenderLuminance.class.getName()); + private static final Logger LOGGER = Logger.getLogger(TextureBlenderLuminance.class.getName()); - public TextureBlenderLuminance(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { - super(flag, negateTexture, blendType, materialColor, color, blendFactor); - } - - public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { - this.prepareImagesForBlending(image, baseImage); - - Format format = image.getFormat(); - PixelInputOutput basePixelIO = null; - TexturePixel basePixel = null; - float[] materialColor = this.materialColor; - if(baseImage != null) { - basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); - materialColor = new float[this.materialColor.length]; - basePixel = new TexturePixel(); - } - - int width = image.getWidth(); - int height = image.getHeight(); - int depth = image.getDepth(); - if (depth == 0) { - depth = 1; - } - ArrayList dataArray = new ArrayList(depth); + public TextureBlenderLuminance(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { + super(flag, negateTexture, blendType, materialColor, color, blendFactor); + } - float[] resultPixel = new float[4]; - float[] tinAndAlpha = new float[2]; - for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { - ByteBuffer data = image.getData(dataLayerIndex); - data.rewind(); - ByteBuffer newData = BufferUtils.createByteBuffer(width * height * 4); - - int dataIndex = 0, x = 0, y = 0; - while (data.hasRemaining()) { - //getting the proper material color if the base texture is applied - if(basePixelIO != null) { - basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y); - basePixel.toRGBA(materialColor); - } - - this.getTinAndAlpha(data, format, negateTexture, tinAndAlpha); - this.blendPixel(resultPixel, materialColor, color, tinAndAlpha[0], blendFactor, blendType, blenderContext); - newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); - newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); - newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); - newData.put(dataIndex++, (byte) (tinAndAlpha[1] * 255.0f)); - - ++x; - if(x >= width) { - x = 0; - ++y; - } - } - dataArray.add(newData); - } - - Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0)); - if(image.getMipMapSizes() != null) { - result.setMipMapSizes(image.getMipMapSizes().clone()); - } - return result; - } + public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { + this.prepareImagesForBlending(image, baseImage); - /** - * This method return texture intensity and alpha value. - * - * @param data - * the texture data - * @param imageFormat - * the image format - * @param neg - * indicates if the texture is negated - * @param result - * the table (2 elements) where the result is being stored - */ - protected void getTinAndAlpha(ByteBuffer data, Format imageFormat, boolean neg, float[] result) { - byte pixelValue = data.get();// at least one byte is always taken - float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - switch (imageFormat) { - case Luminance8: - result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; - result[1] = 1.0f; - break; - case Luminance8Alpha8: - result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; - pixelValue = data.get(); - result[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - break; - case Luminance16: - case Luminance16Alpha16: - case Luminance16F: - case Luminance16FAlpha16F: - case Luminance32F: - LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat); - break; - default: - throw new IllegalStateException("Invalid image format type for Luminance texture blender: " + imageFormat); - } - } + Format format = image.getFormat(); + PixelInputOutput basePixelIO = null; + TexturePixel basePixel = null; + float[] materialColor = this.materialColor; + if (baseImage != null) { + basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); + materialColor = new float[this.materialColor.length]; + basePixel = new TexturePixel(); + } - /** - * This method blends the texture with an appropriate color. - * - * @param result - * the result color (variable 'in' in blender source code) - * @param materialColor - * the texture color (variable 'out' in blender source coude) - * @param color - * the previous color (variable 'tex' in blender source code) - * @param textureIntensity - * texture intensity (variable 'fact' in blender source code) - * @param textureFactor - * texture affection factor (variable 'facg' in blender source - * code) - * @param blendtype - * the blend type - * @param blenderContext - * the blender context - */ - protected void blendPixel(float[] result, float[] materialColor, float[] color, float textureIntensity, float textureFactor, int blendtype, BlenderContext blenderContext) { - float oneMinusFactor, col; - textureIntensity *= textureFactor; + int width = image.getWidth(); + int height = image.getHeight(); + int depth = image.getDepth(); + if (depth == 0) { + depth = 1; + } + ArrayList dataArray = new ArrayList(depth); - switch (blendtype) { - case MTEX_BLEND: - oneMinusFactor = 1.0f - textureIntensity; - result[0] = textureIntensity * color[0] + oneMinusFactor * materialColor[0]; - result[1] = textureIntensity * color[1] + oneMinusFactor * materialColor[1]; - result[2] = textureIntensity * color[2] + oneMinusFactor * materialColor[2]; - break; - case MTEX_MUL: - oneMinusFactor = 1.0f - textureFactor; - result[0] = (oneMinusFactor + textureIntensity * materialColor[0]) * color[0]; - result[1] = (oneMinusFactor + textureIntensity * materialColor[1]) * color[1]; - result[2] = (oneMinusFactor + textureIntensity * materialColor[2]) * color[2]; - break; - case MTEX_DIV: - oneMinusFactor = 1.0f - textureIntensity; - if (color[0] != 0.0) { - result[0] = (oneMinusFactor * materialColor[0] + textureIntensity * materialColor[0] / color[0]) * 0.5f; - } - if (color[1] != 0.0) { - result[1] = (oneMinusFactor * materialColor[1] + textureIntensity * materialColor[1] / color[1]) * 0.5f; - } - if (color[2] != 0.0) { - result[2] = (oneMinusFactor * materialColor[2] + textureIntensity * materialColor[2] / color[2]) * 0.5f; - } - break; - case MTEX_SCREEN: - oneMinusFactor = 1.0f - textureFactor; - result[0] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); - result[1] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); - result[2] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); - break; - case MTEX_OVERLAY: - oneMinusFactor = 1.0f - textureFactor; - if (materialColor[0] < 0.5f) { - result[0] = color[0] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[0]); - } else { - result[0] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); - } - if (materialColor[1] < 0.5f) { - result[1] = color[1] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[1]); - } else { - result[1] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); - } - if (materialColor[2] < 0.5f) { - result[2] = color[2] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[2]); - } else { - result[2] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); - } - break; - case MTEX_SUB: - result[0] = materialColor[0] - textureIntensity * color[0]; - result[1] = materialColor[1] - textureIntensity * color[1]; - result[2] = materialColor[2] - textureIntensity * color[2]; - result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); - result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); - result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); - break; - case MTEX_ADD: - result[0] = (textureIntensity * color[0] + materialColor[0]) * 0.5f; - result[1] = (textureIntensity * color[1] + materialColor[1]) * 0.5f; - result[2] = (textureIntensity * color[2] + materialColor[2]) * 0.5f; - break; - case MTEX_DIFF: - oneMinusFactor = 1.0f - textureIntensity; - result[0] = oneMinusFactor * materialColor[0] + textureIntensity * Math.abs(materialColor[0] - color[0]); - result[1] = oneMinusFactor * materialColor[1] + textureIntensity * Math.abs(materialColor[1] - color[1]); - result[2] = oneMinusFactor * materialColor[2] + textureIntensity * Math.abs(materialColor[2] - color[2]); - break; - case MTEX_DARK: - col = textureIntensity * color[0]; - result[0] = col < materialColor[0] ? col : materialColor[0]; - col = textureIntensity * color[1]; - result[1] = col < materialColor[1] ? col : materialColor[1]; - col = textureIntensity * color[2]; - result[2] = col < materialColor[2] ? col : materialColor[2]; - break; - case MTEX_LIGHT: - col = textureIntensity * color[0]; - result[0] = col > materialColor[0] ? col : materialColor[0]; - col = textureIntensity * color[1]; - result[1] = col > materialColor[1] ? col : materialColor[1]; - col = textureIntensity * color[2]; - result[2] = col > materialColor[2] ? col : materialColor[2]; - break; - case MTEX_BLEND_HUE: - case MTEX_BLEND_SAT: - case MTEX_BLEND_VAL: - case MTEX_BLEND_COLOR: - System.arraycopy(materialColor, 0, result, 0, 3); - this.blendHSV(blendtype, result, textureIntensity, color, blenderContext); - break; - default: - throw new IllegalStateException("Unknown blend type: " + blendtype); - } - } + float[] resultPixel = new float[4]; + float[] tinAndAlpha = new float[2]; + for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { + ByteBuffer data = image.getData(dataLayerIndex); + data.rewind(); + ByteBuffer newData = BufferUtils.createByteBuffer(width * height * 4); + + int dataIndex = 0, x = 0, y = 0; + while (data.hasRemaining()) { + // getting the proper material color if the base texture is applied + if (basePixelIO != null) { + basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y); + basePixel.toRGBA(materialColor); + } + + this.getTinAndAlpha(data, format, negateTexture, tinAndAlpha); + this.blendPixel(resultPixel, materialColor, color, tinAndAlpha[0], blendFactor, blendType, blenderContext); + newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); + newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); + newData.put(dataIndex++, (byte) (tinAndAlpha[1] * 255.0f)); + + ++x; + if (x >= width) { + x = 0; + ++y; + } + } + dataArray.add(newData); + } + + Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray) : new Image(Format.RGBA8, width, height, dataArray.get(0)); + if (image.getMipMapSizes() != null) { + result.setMipMapSizes(image.getMipMapSizes().clone()); + } + return result; + } + + /** + * This method return texture intensity and alpha value. + * + * @param data + * the texture data + * @param imageFormat + * the image format + * @param neg + * indicates if the texture is negated + * @param result + * the table (2 elements) where the result is being stored + */ + protected void getTinAndAlpha(ByteBuffer data, Format imageFormat, boolean neg, float[] result) { + byte pixelValue = data.get();// at least one byte is always taken + float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + switch (imageFormat) { + case Luminance8: + result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; + result[1] = 1.0f; + break; + case Luminance8Alpha8: + result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; + pixelValue = data.get(); + result[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; + break; + case Luminance16: + case Luminance16Alpha16: + case Luminance16F: + case Luminance16FAlpha16F: + case Luminance32F: + LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat); + break; + default: + throw new IllegalStateException("Invalid image format type for Luminance texture blender: " + imageFormat); + } + } + + /** + * This method blends the texture with an appropriate color. + * + * @param result + * the result color (variable 'in' in blender source code) + * @param materialColor + * the texture color (variable 'out' in blender source coude) + * @param color + * the previous color (variable 'tex' in blender source code) + * @param textureIntensity + * texture intensity (variable 'fact' in blender source code) + * @param textureFactor + * texture affection factor (variable 'facg' in blender source + * code) + * @param blendtype + * the blend type + * @param blenderContext + * the blender context + */ + protected void blendPixel(float[] result, float[] materialColor, float[] color, float textureIntensity, float textureFactor, int blendtype, BlenderContext blenderContext) { + float oneMinusFactor, col; + textureIntensity *= textureFactor; + + switch (blendtype) { + case MTEX_BLEND: + oneMinusFactor = 1.0f - textureIntensity; + result[0] = textureIntensity * color[0] + oneMinusFactor * materialColor[0]; + result[1] = textureIntensity * color[1] + oneMinusFactor * materialColor[1]; + result[2] = textureIntensity * color[2] + oneMinusFactor * materialColor[2]; + break; + case MTEX_MUL: + oneMinusFactor = 1.0f - textureFactor; + result[0] = (oneMinusFactor + textureIntensity * materialColor[0]) * color[0]; + result[1] = (oneMinusFactor + textureIntensity * materialColor[1]) * color[1]; + result[2] = (oneMinusFactor + textureIntensity * materialColor[2]) * color[2]; + break; + case MTEX_DIV: + oneMinusFactor = 1.0f - textureIntensity; + if (color[0] != 0.0) { + result[0] = (oneMinusFactor * materialColor[0] + textureIntensity * materialColor[0] / color[0]) * 0.5f; + } + if (color[1] != 0.0) { + result[1] = (oneMinusFactor * materialColor[1] + textureIntensity * materialColor[1] / color[1]) * 0.5f; + } + if (color[2] != 0.0) { + result[2] = (oneMinusFactor * materialColor[2] + textureIntensity * materialColor[2] / color[2]) * 0.5f; + } + break; + case MTEX_SCREEN: + oneMinusFactor = 1.0f - textureFactor; + result[0] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); + result[1] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); + result[2] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); + break; + case MTEX_OVERLAY: + oneMinusFactor = 1.0f - textureFactor; + if (materialColor[0] < 0.5f) { + result[0] = color[0] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[0]); + } else { + result[0] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); + } + if (materialColor[1] < 0.5f) { + result[1] = color[1] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[1]); + } else { + result[1] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); + } + if (materialColor[2] < 0.5f) { + result[2] = color[2] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[2]); + } else { + result[2] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); + } + break; + case MTEX_SUB: + result[0] = materialColor[0] - textureIntensity * color[0]; + result[1] = materialColor[1] - textureIntensity * color[1]; + result[2] = materialColor[2] - textureIntensity * color[2]; + result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); + result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); + result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); + break; + case MTEX_ADD: + result[0] = (textureIntensity * color[0] + materialColor[0]) * 0.5f; + result[1] = (textureIntensity * color[1] + materialColor[1]) * 0.5f; + result[2] = (textureIntensity * color[2] + materialColor[2]) * 0.5f; + break; + case MTEX_DIFF: + oneMinusFactor = 1.0f - textureIntensity; + result[0] = oneMinusFactor * materialColor[0] + textureIntensity * Math.abs(materialColor[0] - color[0]); + result[1] = oneMinusFactor * materialColor[1] + textureIntensity * Math.abs(materialColor[1] - color[1]); + result[2] = oneMinusFactor * materialColor[2] + textureIntensity * Math.abs(materialColor[2] - color[2]); + break; + case MTEX_DARK: + col = textureIntensity * color[0]; + result[0] = col < materialColor[0] ? col : materialColor[0]; + col = textureIntensity * color[1]; + result[1] = col < materialColor[1] ? col : materialColor[1]; + col = textureIntensity * color[2]; + result[2] = col < materialColor[2] ? col : materialColor[2]; + break; + case MTEX_LIGHT: + col = textureIntensity * color[0]; + result[0] = col > materialColor[0] ? col : materialColor[0]; + col = textureIntensity * color[1]; + result[1] = col > materialColor[1] ? col : materialColor[1]; + col = textureIntensity * color[2]; + result[2] = col > materialColor[2] ? col : materialColor[2]; + break; + case MTEX_BLEND_HUE: + case MTEX_BLEND_SAT: + case MTEX_BLEND_VAL: + case MTEX_BLEND_COLOR: + System.arraycopy(materialColor, 0, result, 0, 3); + this.blendHSV(blendtype, result, textureIntensity, color, blenderContext); + break; + default: + throw new IllegalStateException("Unknown blend type: " + blendtype); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java index cad99604b..91df1bf11 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java @@ -51,37 +51,37 @@ import java.util.logging.Logger; * It is only used by TextureHelper. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class NoiseGenerator extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(NoiseGenerator.class.getName()); - +/* package */class NoiseGenerator extends AbstractBlenderHelper { + private static final Logger LOGGER = Logger.getLogger(NoiseGenerator.class.getName()); + // tex->stype - protected static final int TEX_PLASTIC = 0; - protected static final int TEX_WALLIN = 1; - protected static final int TEX_WALLOUT = 2; + protected static final int TEX_PLASTIC = 0; + protected static final int TEX_WALLIN = 1; + protected static final int TEX_WALLOUT = 2; // musgrave stype - protected static final int TEX_MFRACTAL = 0; - protected static final int TEX_RIDGEDMF = 1; - protected static final int TEX_HYBRIDMF = 2; - protected static final int TEX_FBM = 3; - protected static final int TEX_HTERRAIN = 4; + protected static final int TEX_MFRACTAL = 0; + protected static final int TEX_RIDGEDMF = 1; + protected static final int TEX_HYBRIDMF = 2; + protected static final int TEX_FBM = 3; + protected static final int TEX_HTERRAIN = 4; // keyblock->type - protected static final int KEY_LINEAR = 0; - protected static final int KEY_CARDINAL = 1; - protected static final int KEY_BSPLINE = 2; + protected static final int KEY_LINEAR = 0; + protected static final int KEY_CARDINAL = 1; + protected static final int KEY_BSPLINE = 2; // CONSTANTS (read from file) - protected static float[] hashpntf; - protected static short[] hash; - protected static float[] hashvectf; - protected static short[] p; - protected static float[][] g; + protected static float[] hashpntf; + protected static short[] hash; + protected static float[] hashvectf; + protected static short[] p; + protected static float[][] g; /** * Constructor. Stores the blender version number and loads the constants needed for computations. * @param blenderVersion - * the number of blender version + * the number of blender version */ public NoiseGenerator(String blenderVersion) { super(blenderVersion, false); @@ -116,11 +116,11 @@ import java.util.logging.Logger; } } } - - protected static Map noiseFunctions = new HashMap(); + + protected static Map noiseFunctions = new HashMap(); static { noiseFunctions.put(Integer.valueOf(0), new NoiseFunction() { - // originalBlenderNoise + // originalBlenderNoise public float execute(float x, float y, float z) { return NoiseFunctions.originalBlenderNoise(x, y, z); } @@ -130,7 +130,7 @@ import java.util.logging.Logger; } }); noiseFunctions.put(Integer.valueOf(1), new NoiseFunction() { - // orgPerlinNoise + // orgPerlinNoise public float execute(float x, float y, float z) { return 0.5f + 0.5f * NoiseFunctions.noise3Perlin(x, y, z); } @@ -140,7 +140,7 @@ import java.util.logging.Logger; } }); noiseFunctions.put(Integer.valueOf(2), new NoiseFunction() { - // newPerlin + // newPerlin public float execute(float x, float y, float z) { return 0.5f + 0.5f * NoiseFunctions.newPerlin(x, y, z); } @@ -150,7 +150,7 @@ import java.util.logging.Logger; } }); noiseFunctions.put(Integer.valueOf(3), new NoiseFunction() { - // voronoi_F1 + // voronoi_F1 public float execute(float x, float y, float z) { float[] da = new float[4], pa = new float[12]; NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0); @@ -164,7 +164,7 @@ import java.util.logging.Logger; } }); noiseFunctions.put(Integer.valueOf(4), new NoiseFunction() { - // voronoi_F2 + // voronoi_F2 public float execute(float x, float y, float z) { float[] da = new float[4], pa = new float[12]; NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0); @@ -178,7 +178,7 @@ import java.util.logging.Logger; } }); noiseFunctions.put(Integer.valueOf(5), new NoiseFunction() { - // voronoi_F3 + // voronoi_F3 public float execute(float x, float y, float z) { float[] da = new float[4], pa = new float[12]; NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0); @@ -192,7 +192,7 @@ import java.util.logging.Logger; } }); noiseFunctions.put(Integer.valueOf(6), new NoiseFunction() { - // voronoi_F4 + // voronoi_F4 public float execute(float x, float y, float z) { float[] da = new float[4], pa = new float[12]; NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0); @@ -206,7 +206,7 @@ import java.util.logging.Logger; } }); noiseFunctions.put(Integer.valueOf(7), new NoiseFunction() { - // voronoi_F1F2 + // voronoi_F1F2 public float execute(float x, float y, float z) { float[] da = new float[4], pa = new float[12]; NoiseFunctions.voronoi(x, y, z, da, pa, 1, 0); @@ -220,7 +220,7 @@ import java.util.logging.Logger; } }); noiseFunctions.put(Integer.valueOf(8), new NoiseFunction() { - // voronoi_Cr + // voronoi_Cr public float execute(float x, float y, float z) { float t = 10 * noiseFunctions.get(Integer.valueOf(7)).execute(x, y, z);// voronoi_F1F2 return t > 1.0f ? 1.0f : t; @@ -232,7 +232,7 @@ import java.util.logging.Logger; } }); noiseFunctions.put(Integer.valueOf(14), new NoiseFunction() { - // cellNoise + // cellNoise public float execute(float x, float y, float z) { int xi = (int) Math.floor(x); int yi = (int) Math.floor(y); @@ -252,25 +252,25 @@ import java.util.logging.Logger; static { distanceFunctions.put(Integer.valueOf(0), new DistanceFunction() { - // real distance + // real distance public float execute(float x, float y, float z, float e) { return (float) Math.sqrt(x * x + y * y + z * z); } }); distanceFunctions.put(Integer.valueOf(1), new DistanceFunction() { - // distance squared + // distance squared public float execute(float x, float y, float z, float e) { return x * x + y * y + z * z; } }); distanceFunctions.put(Integer.valueOf(2), new DistanceFunction() { - // manhattan/taxicab/cityblock distance + // manhattan/taxicab/cityblock distance public float execute(float x, float y, float z, float e) { return FastMath.abs(x) + FastMath.abs(y) + FastMath.abs(z); } }); distanceFunctions.put(Integer.valueOf(3), new DistanceFunction() { - // Chebychev + // Chebychev public float execute(float x, float y, float z, float e) { x = FastMath.abs(x); y = FastMath.abs(y); @@ -280,14 +280,14 @@ import java.util.logging.Logger; } }); distanceFunctions.put(Integer.valueOf(4), new DistanceFunction() { - // Minkovsky, preset exponent 0.5 (MinkovskyH) + // Minkovsky, preset exponent 0.5 (MinkovskyH) public float execute(float x, float y, float z, float e) { float d = (float) (Math.sqrt(FastMath.abs(x)) + Math.sqrt(FastMath.abs(y)) + Math.sqrt(FastMath.abs(z))); return d * d; } }); distanceFunctions.put(Integer.valueOf(5), new DistanceFunction() { - // Minkovsky, preset exponent 0.25 (Minkovsky4) + // Minkovsky, preset exponent 0.25 (Minkovsky4) public float execute(float x, float y, float z, float e) { x *= x; y *= y; @@ -296,13 +296,13 @@ import java.util.logging.Logger; } }); distanceFunctions.put(Integer.valueOf(6), new DistanceFunction() { - // Minkovsky, general case + // Minkovsky, general case public float execute(float x, float y, float z, float e) { return (float) Math.pow(Math.pow(FastMath.abs(x), e) + Math.pow(FastMath.abs(y), e) + Math.pow(FastMath.abs(z), e), 1.0f / e); } }); } - + protected static Map musgraveFunctions = new HashMap(); static { musgraveFunctions.put(Integer.valueOf(TEX_MFRACTAL), new MusgraveFunction() { @@ -460,64 +460,64 @@ import java.util.logging.Logger; } }); } - + public static class NoiseFunctions { - public static float noise(float x, float y, float z, float noiseSize, int noiseDepth, int noiseBasis, boolean isHard) { - NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis)); - if (abstractNoiseFunc == null) { - abstractNoiseFunc = noiseFunctions.get(0); - noiseBasis = 0; - } - - if (noiseBasis == 0) { - ++x; - ++y; - ++z; - } - - if (noiseSize != 0.0) { - noiseSize = 1.0f / noiseSize; - x *= noiseSize; - y *= noiseSize; - z *= noiseSize; - } - float result = abstractNoiseFunc.execute(x, y, z); - return isHard ? Math.abs(2.0f * result - 1.0f) : result; - } - - public static float turbulence(float x, float y, float z, float noiseSize, int noiseDepth, int noiseBasis, boolean isHard) { - NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis)); - if (abstractNoiseFunc == null) { - abstractNoiseFunc = noiseFunctions.get(0); - noiseBasis = 0; - } - - if (noiseBasis == 0) { - ++x; - ++y; - ++z; - } - if (noiseSize != 0.0) { - noiseSize = 1.0f / noiseSize; - x *= noiseSize; - y *= noiseSize; - z *= noiseSize; - } - - float sum = 0, t, amp = 1, fscale = 1; - for (int i = 0; i <= noiseDepth; ++i, amp *= 0.5, fscale *= 2) { - t = abstractNoiseFunc.execute(fscale * x, fscale * y, fscale * z); - if (isHard) { - t = FastMath.abs(2.0f * t - 1.0f); - } - sum += t * amp; - } - - sum *= (float) (1 << noiseDepth) / (float) ((1 << noiseDepth + 1) - 1); - return sum; - } - - /** + public static float noise(float x, float y, float z, float noiseSize, int noiseDepth, int noiseBasis, boolean isHard) { + NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis)); + if (abstractNoiseFunc == null) { + abstractNoiseFunc = noiseFunctions.get(0); + noiseBasis = 0; + } + + if (noiseBasis == 0) { + ++x; + ++y; + ++z; + } + + if (noiseSize != 0.0) { + noiseSize = 1.0f / noiseSize; + x *= noiseSize; + y *= noiseSize; + z *= noiseSize; + } + float result = abstractNoiseFunc.execute(x, y, z); + return isHard ? Math.abs(2.0f * result - 1.0f) : result; + } + + public static float turbulence(float x, float y, float z, float noiseSize, int noiseDepth, int noiseBasis, boolean isHard) { + NoiseFunction abstractNoiseFunc = noiseFunctions.get(Integer.valueOf(noiseBasis)); + if (abstractNoiseFunc == null) { + abstractNoiseFunc = noiseFunctions.get(0); + noiseBasis = 0; + } + + if (noiseBasis == 0) { + ++x; + ++y; + ++z; + } + if (noiseSize != 0.0) { + noiseSize = 1.0f / noiseSize; + x *= noiseSize; + y *= noiseSize; + z *= noiseSize; + } + + float sum = 0, t, amp = 1, fscale = 1; + for (int i = 0; i <= noiseDepth; ++i, amp *= 0.5, fscale *= 2) { + t = abstractNoiseFunc.execute(fscale * x, fscale * y, fscale * z); + if (isHard) { + t = FastMath.abs(2.0f * t - 1.0f); + } + sum += t * amp; + } + + sum *= (float) (1 << noiseDepth) / (float) ((1 << noiseDepth + 1) - 1); + return sum; + } + + /** * Not 'pure' Worley, but the results are virtually the same. Returns distances in da and point coords in pa */ public static void voronoi(float x, float y, float z, float[] da, float[] pa, float distanceExponent, int distanceType) { @@ -531,7 +531,7 @@ import java.util.logging.Logger; int xi = (int) FastMath.floor(x); int yi = (int) FastMath.floor(y); int zi = (int) FastMath.floor(z); - da[0] = da[1] = da[2] = da[3] = Float.MAX_VALUE;//1e10f; + da[0] = da[1] = da[2] = da[3] = Float.MAX_VALUE;// 1e10f; for (int i = xi - 1; i <= xi + 1; ++i) { for (int j = yi - 1; j <= yi + 1; ++j) { for (int k = zi - 1; k <= zi + 1; ++k) { @@ -589,7 +589,7 @@ import java.util.logging.Logger; } } } - + // instead of adding another permutation array, just use hash table defined above public static float newPerlin(float x, float y, float z) { int A, AA, AB, B, BA, BB; @@ -598,7 +598,7 @@ import java.util.logging.Logger; x -= floorX; y -= floorY; z -= floorZ; - //computing fading curves + // computing fading curves floorX = NoiseMath.npfade(x); floorY = NoiseMath.npfade(y); floorZ = NoiseMath.npfade(z); @@ -608,14 +608,8 @@ import java.util.logging.Logger; B = hash[intX + 1] + intY; BA = hash[B] + intZ; BB = hash[B + 1] + intZ; - return NoiseMath.lerp(floorZ, NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA], x, y, z), - NoiseMath.grad(hash[BA], x - 1, y, z)), - NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB], x, y - 1, z), - NoiseMath.grad(hash[BB], x - 1, y - 1, z))), - NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA + 1], x, y, z - 1), - NoiseMath.grad(hash[BA + 1], x - 1, y, z - 1)), - NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB + 1], x, y - 1, z - 1), - NoiseMath.grad(hash[BB + 1], x - 1, y - 1, z - 1)))); + return NoiseMath.lerp(floorZ, NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA], x, y, z), NoiseMath.grad(hash[BA], x - 1, y, z)), NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB], x, y - 1, z), NoiseMath.grad(hash[BB], x - 1, y - 1, z))), + NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA + 1], x, y, z - 1), NoiseMath.grad(hash[BA + 1], x - 1, y, z - 1)), NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB + 1], x, y - 1, z - 1), NoiseMath.grad(hash[BB + 1], x - 1, y - 1, z - 1)))); } public static float noise3Perlin(float x, float y, float z) { @@ -624,13 +618,13 @@ import java.util.logging.Logger; int bx1 = bx0 + 1 & 0xFF; float rx0 = t - (int) t; float rx1 = rx0 - 1.0f; - + t = y + 10000.0f; int by0 = (int) t & 0xFF; int by1 = by0 + 1 & 0xFF; float ry0 = t - (int) t; float ry1 = ry0 - 1.0f; - + t = z + 10000.0f; int bz0 = (int) t & 0xFF; int bz1 = bz0 + 1 & 0xFF; @@ -685,7 +679,7 @@ import java.util.logging.Logger; int ix = (int) Math.floor(x); int iy = (int) Math.floor(y); int iz = (int) Math.floor(z); - + float ox = x - ix; float oy = y - iy; float oz = z - iz; @@ -707,24 +701,23 @@ import java.util.logging.Logger; cn4 = 1.0f - 3.0f * cn4 - 2.0f * cn4 * jx; cn5 = 1.0f - 3.0f * cn5 - 2.0f * cn5 * jy; cn6 = 1.0f - 3.0f * cn6 - 2.0f * cn6 * jz; - float[] cn = new float[] {cn1 * cn2 * cn3, cn1 * cn2 * cn6, cn1 * cn5 * cn3, cn1 * cn5 * cn6, - cn4 * cn2 * cn3, cn4 * cn2 * cn6, cn4 * cn5 * cn3, cn4 * cn5 * cn6,}; - + float[] cn = new float[] { cn1 * cn2 * cn3, cn1 * cn2 * cn6, cn1 * cn5 * cn3, cn1 * cn5 * cn6, cn4 * cn2 * cn3, cn4 * cn2 * cn6, cn4 * cn5 * cn3, cn4 * cn5 * cn6, }; + int b00 = hash[hash[ix & 0xFF] + (iy & 0xFF)]; int b01 = hash[hash[ix & 0xFF] + (iy + 1 & 0xFF)]; int b10 = hash[hash[ix + 1 & 0xFF] + (iy & 0xFF)]; int b11 = hash[hash[ix + 1 & 0xFF] + (iy + 1 & 0xFF)]; - int[] b1 = new int[] {b00, b00, b01, b01, b10, b10, b11, b11}; - - int[] b2 = new int[] {iz & 0xFF, iz + 1 & 0xFF}; - - float[] xFactor = new float[] {ox, ox, ox, ox, jx, jx, jx, jx}; - float[] yFactor = new float[] {oy, oy, jy, jy, oy, oy, jy, jy}; - float[] zFactor = new float[] {oz, jz, oz, jz, oz, jz, oz, jz}; - - for(int i=0;i<8;++i) { - int hIndex = 3 * hash[b1[i] + b2[i%2]]; - n += cn[i] * (hashvectf[hIndex] * xFactor[i] + hashvectf[hIndex + 1] * yFactor[i] + hashvectf[hIndex + 2] * zFactor[i]); + int[] b1 = new int[] { b00, b00, b01, b01, b10, b10, b11, b11 }; + + int[] b2 = new int[] { iz & 0xFF, iz + 1 & 0xFF }; + + float[] xFactor = new float[] { ox, ox, ox, ox, jx, jx, jx, jx }; + float[] yFactor = new float[] { oy, oy, jy, jy, oy, oy, jy, jy }; + float[] zFactor = new float[] { oz, jz, oz, jz, oz, jz, oz, jz }; + + for (int i = 0; i < 8; ++i) { + int hIndex = 3 * hash[b1[i] + b2[i % 2]]; + n += cn[i] * (hashvectf[hIndex] * xFactor[i] + hashvectf[hIndex + 1] * yFactor[i] + hashvectf[hIndex + 2] * zFactor[i]); } if (n < 0.0f) { @@ -746,11 +739,11 @@ import java.util.logging.Logger; /** * This method calculates the unsigned value of the noise. * @param x - * the x texture coordinate + * the x texture coordinate * @param y - * the y texture coordinate + * the y texture coordinate * @param z - * the z texture coordinate + * the z texture coordinate * @return value of the noise */ float execute(float x, float y, float z); @@ -758,49 +751,49 @@ import java.util.logging.Logger; /** * This method calculates the signed value of the noise. * @param x - * the x texture coordinate + * the x texture coordinate * @param y - * the y texture coordinate + * the y texture coordinate * @param z - * the z texture coordinate + * the z texture coordinate * @return value of the noise */ float executeSigned(float x, float y, float z); } - + public static class NoiseMath { - public static float lerp(float t, float a, float b) { + public static float lerp(float t, float a, float b) { return a + t * (b - a); } - public static float npfade(float t) { + public static float npfade(float t) { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); } - public static float grad(int hash, float x, float y, float z) { + public static float grad(int hash, float x, float y, float z) { int h = hash & 0x0F; float u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z; return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); } - public static float surve(float t) { + public static float surve(float t) { return t * t * (3.0f - 2.0f * t); } - public static float at(float x, float y, float z, float[] q) { + public static float at(float x, float y, float z, float[] q) { return x * q[0] + y * q[1] + z * q[2]; } - - public static void hash(int x, int y, int z, float[] result) { + + public static void hash(int x, int y, int z, float[] result) { result[0] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF]]; result[1] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 1]; result[2] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 2]; } } - + @Override public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) { - return true; + return true; } /** @@ -812,13 +805,13 @@ import java.util.logging.Logger; /** * This method calculates the distance for voronoi algorithms. * @param x - * the x coordinate + * the x coordinate * @param y - * the y coordinate + * the y coordinate * @param z - * the z coordinate + * the z coordinate * @param e - * this parameter used in Monkovsky (no idea what it really is ;) + * this parameter used in Monkovsky (no idea what it really is ;) * @return */ float execute(float x, float y, float z, float e); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java index 5de1b1114..c001e463c 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java @@ -42,43 +42,44 @@ import com.jme3.texture.Image.Format; * @author Marcin Roguski (Kaelthas) */ public abstract class TextureGenerator { - protected NoiseGenerator noiseGenerator; - protected int flag; - protected float[][] colorBand; - protected BrightnessAndContrastData bacd; - protected Format imageFormat; - - public TextureGenerator(NoiseGenerator noiseGenerator, Format imageFormat) { - this.noiseGenerator = noiseGenerator; - this.imageFormat = imageFormat; - } + protected NoiseGenerator noiseGenerator; + protected int flag; + protected float[][] colorBand; + protected BrightnessAndContrastData bacd; + protected Format imageFormat; - public Format getImageFormat() { - return imageFormat; - } - - public void readData(Structure tex, BlenderContext blenderContext) { - flag = ((Number) tex.getFieldValue("flag")).intValue(); - colorBand = new ColorBand(tex, blenderContext).computeValues(); - bacd = new BrightnessAndContrastData(tex); - if(colorBand != null) { - imageFormat = Format.RGBA8; - } - } - - public abstract void getPixel(TexturePixel pixel, float x, float y, float z); - - /** - * This method applies brightness and contrast for RGB textures. - * @param tex texture structure - * @param texres - */ - protected void applyBrightnessAndContrast(BrightnessAndContrastData bacd, TexturePixel texres) { + public TextureGenerator(NoiseGenerator noiseGenerator, Format imageFormat) { + this.noiseGenerator = noiseGenerator; + this.imageFormat = imageFormat; + } + + public Format getImageFormat() { + return imageFormat; + } + + public void readData(Structure tex, BlenderContext blenderContext) { + flag = ((Number) tex.getFieldValue("flag")).intValue(); + colorBand = new ColorBand(tex, blenderContext).computeValues(); + bacd = new BrightnessAndContrastData(tex); + if (colorBand != null) { + imageFormat = Format.RGBA8; + } + } + + public abstract void getPixel(TexturePixel pixel, float x, float y, float z); + + /** + * This method applies brightness and contrast for RGB textures. + * @param tex + * texture structure + * @param texres + */ + protected void applyBrightnessAndContrast(BrightnessAndContrastData bacd, TexturePixel texres) { texres.red = (texres.red - 0.5f) * bacd.contrast + bacd.brightness; if (texres.red < 0.0f) { texres.red = 0.0f; } - texres.green =(texres.green - 0.5f) * bacd.contrast + bacd.brightness; + texres.green = (texres.green - 0.5f) * bacd.contrast + bacd.brightness; if (texres.green < 0.0f) { texres.green = 0.0f; } @@ -87,14 +88,14 @@ public abstract class TextureGenerator { texres.blue = 0.0f; } } - - /** - * This method applies brightness and contrast for Luminance textures. - * @param texres - * @param contrast - * @param brightness - */ - protected void applyBrightnessAndContrast(TexturePixel texres, float contrast, float brightness) { + + /** + * This method applies brightness and contrast for Luminance textures. + * @param texres + * @param contrast + * @param brightness + */ + protected void applyBrightnessAndContrast(TexturePixel texres, float contrast, float brightness) { texres.intensity = (texres.intensity - 0.5f) * contrast + brightness; if (texres.intensity < 0.0f) { texres.intensity = 0.0f; @@ -102,28 +103,29 @@ public abstract class TextureGenerator { texres.intensity = 1.0f; } } - - /** - * This class contains brightness and contrast data. - * @author Marcin Roguski (Kaelthas) - */ - protected static class BrightnessAndContrastData { - public final float contrast; + + /** + * This class contains brightness and contrast data. + * @author Marcin Roguski (Kaelthas) + */ + protected static class BrightnessAndContrastData { + public final float contrast; public final float brightness; public final float rFactor; public final float gFactor; public final float bFactor; - + /** * Constructor reads the required data from the given structure. - * @param tex texture structure + * @param tex + * texture structure */ - public BrightnessAndContrastData(Structure tex) { - contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); - brightness = ((Number) tex.getFieldValue("bright")).floatValue() - 0.5f; - rFactor = ((Number) tex.getFieldValue("rfac")).floatValue(); - gFactor = ((Number) tex.getFieldValue("gfac")).floatValue(); - bFactor = ((Number) tex.getFieldValue("bfac")).floatValue(); - } - } + public BrightnessAndContrastData(Structure tex) { + contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); + brightness = ((Number) tex.getFieldValue("bright")).floatValue() - 0.5f; + rFactor = ((Number) tex.getFieldValue("rfac")).floatValue(); + gFactor = ((Number) tex.getFieldValue("gfac")).floatValue(); + bFactor = ((Number) tex.getFieldValue("bfac")).floatValue(); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java index a7b2b578b..eabe6fa74 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java @@ -42,90 +42,90 @@ import com.jme3.texture.Image.Format; * @author Marcin Roguski (Kaelthas) */ public final class TextureGeneratorBlend extends TextureGenerator { - + private static final IntensityFunction INTENSITY_FUNCTION[] = new IntensityFunction[7]; static { - INTENSITY_FUNCTION[0] = new IntensityFunction() {//Linear: stype = 0 (TEX_LIN) - public float getIntensity(float x, float y, float z) { - return (1.0f + x) * 0.5f; - } - }; - INTENSITY_FUNCTION[1] = new IntensityFunction() {//Quad: stype = 1 (TEX_QUAD) - public float getIntensity(float x, float y, float z) { - float result = (1.0f + x) * 0.5f; - return result * result; - } - }; - INTENSITY_FUNCTION[2] = new IntensityFunction() {//Ease: stype = 2 (TEX_EASE) - public float getIntensity(float x, float y, float z) { - float result = (1.0f + x) * 0.5f; - if (result <= 0.0f) { - return 0.0f; - } else if (result >= 1.0f) { - return 1.0f; - } else { - return result * result *(3.0f - 2.0f * result); - } - } - }; - INTENSITY_FUNCTION[3] = new IntensityFunction() {//Diagonal: stype = 3 (TEX_DIAG) - public float getIntensity(float x, float y, float z) { - return (2.0f + x + y) * 0.25f; - } - }; - INTENSITY_FUNCTION[4] = new IntensityFunction() {//Sphere: stype = 4 (TEX_SPHERE) - public float getIntensity(float x, float y, float z) { - float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z); - return result < 0.0f ? 0.0f : result; - } - }; - INTENSITY_FUNCTION[5] = new IntensityFunction() {//Halo: stype = 5 (TEX_HALO) - public float getIntensity(float x, float y, float z) { - float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z); - return result <= 0.0f ? 0.0f : result * result; - } - }; - INTENSITY_FUNCTION[6] = new IntensityFunction() {//Radial: stype = 6 (TEX_RAD) - public float getIntensity(float x, float y, float z) { - return (float) Math.atan2(y, x) * FastMath.INV_TWO_PI + 0.5f; - } - }; + INTENSITY_FUNCTION[0] = new IntensityFunction() {// Linear: stype = 0 (TEX_LIN) + public float getIntensity(float x, float y, float z) { + return (1.0f + x) * 0.5f; + } + }; + INTENSITY_FUNCTION[1] = new IntensityFunction() {// Quad: stype = 1 (TEX_QUAD) + public float getIntensity(float x, float y, float z) { + float result = (1.0f + x) * 0.5f; + return result * result; + } + }; + INTENSITY_FUNCTION[2] = new IntensityFunction() {// Ease: stype = 2 (TEX_EASE) + public float getIntensity(float x, float y, float z) { + float result = (1.0f + x) * 0.5f; + if (result <= 0.0f) { + return 0.0f; + } else if (result >= 1.0f) { + return 1.0f; + } else { + return result * result * (3.0f - 2.0f * result); + } + } + }; + INTENSITY_FUNCTION[3] = new IntensityFunction() {// Diagonal: stype = 3 (TEX_DIAG) + public float getIntensity(float x, float y, float z) { + return (2.0f + x + y) * 0.25f; + } + }; + INTENSITY_FUNCTION[4] = new IntensityFunction() {// Sphere: stype = 4 (TEX_SPHERE) + public float getIntensity(float x, float y, float z) { + float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z); + return result < 0.0f ? 0.0f : result; + } + }; + INTENSITY_FUNCTION[5] = new IntensityFunction() {// Halo: stype = 5 (TEX_HALO) + public float getIntensity(float x, float y, float z) { + float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z); + return result <= 0.0f ? 0.0f : result * result; + } + }; + INTENSITY_FUNCTION[6] = new IntensityFunction() {// Radial: stype = 6 (TEX_RAD) + public float getIntensity(float x, float y, float z) { + return (float) Math.atan2(y, x) * FastMath.INV_TWO_PI + 0.5f; + } + }; + } + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorBlend(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + protected int stype; + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + stype = ((Number) tex.getFieldValue("stype")).intValue(); } - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorBlend(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - protected int stype; - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - stype = ((Number) tex.getFieldValue("stype")).intValue(); - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - pixel.intensity = INTENSITY_FUNCTION[stype].getIntensity(x, y, z); - - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - - this.applyBrightnessAndContrast(bacd, pixel); - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } - private static interface IntensityFunction { - float getIntensity(float x, float y, float z); - } + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + pixel.intensity = INTENSITY_FUNCTION[stype].getIntensity(x, y, z); + + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(bacd, pixel); + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } + + private static interface IntensityFunction { + float getIntensity(float x, float y, float z); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java index b57be0ae6..0c118da18 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java @@ -42,68 +42,68 @@ import com.jme3.texture.Image.Format; * @author Marcin Roguski (Kaelthas) */ public class TextureGeneratorClouds extends TextureGenerator { - // noiseType + // noiseType protected static final int TEX_NOISESOFT = 0; protected static final int TEX_NOISEPERL = 1; - + // sType - protected static final int TEX_DEFAULT = 0; - protected static final int TEX_COLOR = 1; - - protected float noisesize; - protected int noiseDepth; - protected int noiseBasis; - protected int noiseType; - protected boolean isHard; - protected int sType; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorClouds(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - noiseDepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); - noiseBasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); - noiseType = ((Number) tex.getFieldValue("noisetype")).intValue(); - isHard = noiseType != TEX_NOISESOFT; - sType = ((Number) tex.getFieldValue("stype")).intValue(); - if(sType == TEX_COLOR) { - this.imageFormat = Format.RGBA8; - } - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - pixel.intensity = NoiseGenerator.NoiseFunctions.turbulence(x, y, z, noisesize, noiseDepth, noiseBasis, isHard); - pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f); - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - pixel.alpha = colorBand[colorbandIndex][3]; - - this.applyBrightnessAndContrast(bacd, pixel); - } else if (sType == TEX_COLOR) { - pixel.red = pixel.intensity; - pixel.green = NoiseGenerator.NoiseFunctions.turbulence(y, x, z, noisesize, noiseDepth, noiseBasis, isHard); - pixel.blue = NoiseGenerator.NoiseFunctions.turbulence(y, z, x, noisesize, noiseDepth, noiseBasis, isHard); - - pixel.green = FastMath.clamp(pixel.green, 0.0f, 1.0f); - pixel.blue = FastMath.clamp(pixel.blue, 0.0f, 1.0f); - pixel.alpha = 1.0f; - - this.applyBrightnessAndContrast(bacd, pixel); - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } + protected static final int TEX_DEFAULT = 0; + protected static final int TEX_COLOR = 1; + + protected float noisesize; + protected int noiseDepth; + protected int noiseBasis; + protected int noiseType; + protected boolean isHard; + protected int sType; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorClouds(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + noiseDepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); + noiseBasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + noiseType = ((Number) tex.getFieldValue("noisetype")).intValue(); + isHard = noiseType != TEX_NOISESOFT; + sType = ((Number) tex.getFieldValue("stype")).intValue(); + if (sType == TEX_COLOR) { + this.imageFormat = Format.RGBA8; + } + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + pixel.intensity = NoiseGenerator.NoiseFunctions.turbulence(x, y, z, noisesize, noiseDepth, noiseBasis, isHard); + pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f); + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + pixel.alpha = colorBand[colorbandIndex][3]; + + this.applyBrightnessAndContrast(bacd, pixel); + } else if (sType == TEX_COLOR) { + pixel.red = pixel.intensity; + pixel.green = NoiseGenerator.NoiseFunctions.turbulence(y, x, z, noisesize, noiseDepth, noiseBasis, isHard); + pixel.blue = NoiseGenerator.NoiseFunctions.turbulence(y, z, x, noisesize, noiseDepth, noiseBasis, isHard); + + pixel.green = FastMath.clamp(pixel.green, 0.0f, 1.0f); + pixel.blue = FastMath.clamp(pixel.blue, 0.0f, 1.0f); + pixel.alpha = 1.0f; + + this.applyBrightnessAndContrast(bacd, pixel); + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java index daf4d5eb6..232ccbeb3 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java @@ -43,46 +43,46 @@ import com.jme3.texture.Image.Format; * @author Marcin Roguski (Kaelthas) */ public class TextureGeneratorDistnoise extends TextureGenerator { - protected float noisesize; - protected float distAmount; - protected int noisebasis; - protected int noisebasis2; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorDistnoise(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - distAmount = ((Number) tex.getFieldValue("dist_amount")).floatValue(); - noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); - noisebasis2 = ((Number) tex.getFieldValue("noisebasis2")).intValue(); - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - pixel.intensity = this.musgraveVariableLunacrityNoise(x * 4, y * 4, z * 4, distAmount, noisebasis, noisebasis2); - pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f); - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; + protected float noisesize; + protected float distAmount; + protected int noisebasis; + protected int noisebasis2; - this.applyBrightnessAndContrast(bacd, pixel); - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorDistnoise(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + distAmount = ((Number) tex.getFieldValue("dist_amount")).floatValue(); + noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + noisebasis2 = ((Number) tex.getFieldValue("noisebasis2")).intValue(); + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + pixel.intensity = this.musgraveVariableLunacrityNoise(x * 4, y * 4, z * 4, distAmount, noisebasis, noisebasis2); + pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f); + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(bacd, pixel); + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } - /** + /** * "Variable Lacunarity Noise" A distorted variety of Perlin noise. This method is used to calculate distorted noise * texture. * @param x @@ -106,6 +106,6 @@ public class TextureGeneratorDistnoise extends TextureGenerator { float rx = abstractNoiseFunc1.execute(x + 13.5f, y + 13.5f, z + 13.5f) * distortion; float ry = abstractNoiseFunc1.execute(x, y, z) * distortion; float rz = abstractNoiseFunc1.execute(x - 13.5f, y - 13.5f, z - 13.5f) * distortion; - return abstractNoiseFunc2.executeSigned(x + rx, y + ry, z + rz); //distorted-domain noise + return abstractNoiseFunc2.executeSigned(x + rx, y + ry, z + rz); // distorted-domain noise } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java index c580863a8..3b6536597 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java @@ -3,37 +3,37 @@ package com.jme3.scene.plugins.blender.textures.generating; import com.jme3.scene.plugins.blender.textures.TextureHelper; public class TextureGeneratorFactory { - - private NoiseGenerator noiseGenerator; - - public TextureGeneratorFactory(String blenderVersion) { - noiseGenerator = new NoiseGenerator(blenderVersion); - } - - public TextureGenerator createTextureGenerator(int generatedTexture) { - switch(generatedTexture) { - case TextureHelper.TEX_BLEND: - return new TextureGeneratorBlend(noiseGenerator); - case TextureHelper.TEX_CLOUDS: - return new TextureGeneratorClouds(noiseGenerator); - case TextureHelper.TEX_DISTNOISE: - return new TextureGeneratorDistnoise(noiseGenerator); - case TextureHelper.TEX_MAGIC: - return new TextureGeneratorMagic(noiseGenerator); - case TextureHelper.TEX_MARBLE: - return new TextureGeneratorMarble(noiseGenerator); - case TextureHelper.TEX_MUSGRAVE: - return new TextureGeneratorMusgrave(noiseGenerator); - case TextureHelper.TEX_NOISE: - return new TextureGeneratorNoise(noiseGenerator); - case TextureHelper.TEX_STUCCI: - return new TextureGeneratorStucci(noiseGenerator); - case TextureHelper.TEX_VORONOI: - return new TextureGeneratorVoronoi(noiseGenerator); - case TextureHelper.TEX_WOOD: - return new TextureGeneratorWood(noiseGenerator); - default: - throw new IllegalStateException("Unknown generated texture type: " + generatedTexture); - } - } + + private NoiseGenerator noiseGenerator; + + public TextureGeneratorFactory(String blenderVersion) { + noiseGenerator = new NoiseGenerator(blenderVersion); + } + + public TextureGenerator createTextureGenerator(int generatedTexture) { + switch (generatedTexture) { + case TextureHelper.TEX_BLEND: + return new TextureGeneratorBlend(noiseGenerator); + case TextureHelper.TEX_CLOUDS: + return new TextureGeneratorClouds(noiseGenerator); + case TextureHelper.TEX_DISTNOISE: + return new TextureGeneratorDistnoise(noiseGenerator); + case TextureHelper.TEX_MAGIC: + return new TextureGeneratorMagic(noiseGenerator); + case TextureHelper.TEX_MARBLE: + return new TextureGeneratorMarble(noiseGenerator); + case TextureHelper.TEX_MUSGRAVE: + return new TextureGeneratorMusgrave(noiseGenerator); + case TextureHelper.TEX_NOISE: + return new TextureGeneratorNoise(noiseGenerator); + case TextureHelper.TEX_STUCCI: + return new TextureGeneratorStucci(noiseGenerator); + case TextureHelper.TEX_VORONOI: + return new TextureGeneratorVoronoi(noiseGenerator); + case TextureHelper.TEX_WOOD: + return new TextureGeneratorWood(noiseGenerator); + default: + throw new IllegalStateException("Unknown generated texture type: " + generatedTexture); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java index 34bdab0d3..6f3d7b599 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java @@ -42,119 +42,119 @@ import com.jme3.texture.Image.Format; * @author Marcin Roguski (Kaelthas) */ public class TextureGeneratorMagic extends TextureGenerator { - private static NoiseDepthFunction[] noiseDepthFunctions = new NoiseDepthFunction[10]; - static { - noiseDepthFunctions[0] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[1] = -(float) Math.cos(xyz[0] - xyz[1] + xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[1] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[0] = (float) Math.cos(xyz[0] - xyz[1] - xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[2] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[2] = (float) Math.sin(-xyz[0] - xyz[1] - xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[3] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[0] = -(float) Math.cos(-xyz[0] + xyz[1] - xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[4] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[1] = -(float) Math.sin(-xyz[0] + xyz[1] + xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[5] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[1] = -(float) Math.cos(-xyz[0] + xyz[1] + xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[6] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[0] = (float) Math.cos(xyz[0] + xyz[1] + xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[7] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[2] = (float) Math.sin(xyz[0] + xyz[1] - xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[8] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[0] = -(float) Math.cos(-xyz[0] - xyz[1] + xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[9] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[1] = -(float) Math.sin(xyz[0] - xyz[1] + xyz[2]) * turbulence; - } - }; - } - - protected int noisedepth; - protected float turbul; - protected float[] xyz = new float[3]; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorMagic(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.RGBA8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); - turbul = ((Number) tex.getFieldValue("turbul")).floatValue() / 5.0f; - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - float turb = turbul; - xyz[0] = (float) Math.sin((x + y + z) * 5.0f); - xyz[1] = (float) Math.cos((-x + y - z) * 5.0f); - xyz[2] = -(float) Math.cos((-x - y + z) * 5.0f); + private static NoiseDepthFunction[] noiseDepthFunctions = new NoiseDepthFunction[10]; + static { + noiseDepthFunctions[0] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[1] = -(float) Math.cos(xyz[0] - xyz[1] + xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[1] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[0] = (float) Math.cos(xyz[0] - xyz[1] - xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[2] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[2] = (float) Math.sin(-xyz[0] - xyz[1] - xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[3] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[0] = -(float) Math.cos(-xyz[0] + xyz[1] - xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[4] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[1] = -(float) Math.sin(-xyz[0] + xyz[1] + xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[5] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[1] = -(float) Math.cos(-xyz[0] + xyz[1] + xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[6] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[0] = (float) Math.cos(xyz[0] + xyz[1] + xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[7] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[2] = (float) Math.sin(xyz[0] + xyz[1] - xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[8] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[0] = -(float) Math.cos(-xyz[0] - xyz[1] + xyz[2]) * turbulence; + } + }; + noiseDepthFunctions[9] = new NoiseDepthFunction() { + public void compute(float[] xyz, float turbulence) { + xyz[1] = -(float) Math.sin(xyz[0] - xyz[1] + xyz[2]) * turbulence; + } + }; + } - if (colorBand != null) { - pixel.intensity = FastMath.clamp(0.3333f * (xyz[0] + xyz[1] + xyz[2]), 0.0f, 1.0f); - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - pixel.alpha = colorBand[colorbandIndex][3]; - } else { - if (noisedepth > 0) { - xyz[0] *= turb; - xyz[1] *= turb; - xyz[2] *= turb; - for (int m=0;m 0) { + xyz[0] *= turb; + xyz[1] *= turb; + xyz[2] *= turb; + for (int m = 0; m < noisedepth; ++m) { + noiseDepthFunctions[m].compute(xyz, turb); + } + } + + if (turb != 0.0f) { + turb *= 2.0f; + xyz[0] /= turb; + xyz[1] /= turb; + xyz[2] /= turb; + } + pixel.red = 0.5f - xyz[0]; + pixel.green = 0.5f - xyz[1]; + pixel.blue = 0.5f - xyz[2]; + pixel.alpha = 1.0f; + } + this.applyBrightnessAndContrast(bacd, pixel); + } + + private static interface NoiseDepthFunction { + void compute(float[] xyz, float turbulence); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java index a874f6009..80822a028 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java @@ -40,50 +40,50 @@ import com.jme3.scene.plugins.blender.textures.TexturePixel; * @author Marcin Roguski (Kaelthas) */ public class TextureGeneratorMarble extends TextureGeneratorWood { - // tex->stype - protected static final int TEX_SOFT = 0; - protected static final int TEX_SHARP = 1; + // tex->stype + protected static final int TEX_SOFT = 0; + protected static final int TEX_SHARP = 1; protected static final int TEX_SHARPER = 2; - - protected MarbleData marbleData; - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorMarble(NoiseGenerator noiseGenerator) { - super(noiseGenerator); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - marbleData = new MarbleData(tex); - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - pixel.intensity = this.marbleInt(marbleData, x, y, z); - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - - this.applyBrightnessAndContrast(bacd, pixel); - pixel.alpha = colorBand[colorbandIndex][3]; - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } + protected MarbleData marbleData; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorMarble(NoiseGenerator noiseGenerator) { + super(noiseGenerator); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + marbleData = new MarbleData(tex); + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + pixel.intensity = this.marbleInt(marbleData, x, y, z); + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(bacd, pixel); + pixel.alpha = colorBand[colorbandIndex][3]; + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } public float marbleInt(MarbleData marbleData, float x, float y, float z) { - int waveform; + int waveform; if (marbleData.waveform > TEX_TRI || marbleData.waveform < TEX_SIN) { - waveform = 0; + waveform = 0; } else { - waveform = marbleData.waveform; + waveform = marbleData.waveform; } float n = 5.0f * (x + y + z); @@ -99,18 +99,18 @@ public class TextureGeneratorMarble extends TextureGeneratorWood { } return mi; } - + private static class MarbleData { - public final float noisesize; - public final int noisebasis; - public final int noisedepth; - public final int stype; - public final float turbul; - public final int waveform; - public final boolean isHard; - + public final float noisesize; + public final int noisebasis; + public final int noisedepth; + public final int stype; + public final float turbul; + public final int waveform; + public final boolean isHard; + public MarbleData(Structure tex) { - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); stype = ((Number) tex.getFieldValue("stype")).intValue(); @@ -118,6 +118,6 @@ public class TextureGeneratorMarble extends TextureGeneratorWood { int noisetype = ((Number) tex.getFieldValue("noisetype")).intValue(); waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue(); isHard = noisetype != TEX_NOISESOFT; - } + } } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMusgrave.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMusgrave.java index 8c36ae987..ad41fd7c5 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMusgrave.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMusgrave.java @@ -42,66 +42,66 @@ import com.jme3.texture.Image.Format; * @author Marcin Roguski (Kaelthas) */ public class TextureGeneratorMusgrave extends TextureGenerator { - protected MusgraveData musgraveData; - protected MusgraveFunction musgraveFunction; - protected int stype; - protected float noisesize; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorMusgrave(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - musgraveData = new MusgraveData(tex); - stype = ((Number) tex.getFieldValue("stype")).intValue(); - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - musgraveFunction = NoiseGenerator.musgraveFunctions.get(Integer.valueOf(musgraveData.stype)); - if(musgraveFunction==null) { - throw new IllegalStateException("Unknown type of musgrave texture: " + stype); - } - } + protected MusgraveData musgraveData; + protected MusgraveFunction musgraveFunction; + protected int stype; + protected float noisesize; + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorMusgrave(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + musgraveData = new MusgraveData(tex); + stype = ((Number) tex.getFieldValue("stype")).intValue(); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + musgraveFunction = NoiseGenerator.musgraveFunctions.get(Integer.valueOf(musgraveData.stype)); + if (musgraveFunction == null) { + throw new IllegalStateException("Unknown type of musgrave texture: " + stype); + } + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + pixel.intensity = musgraveData.outscale * musgraveFunction.execute(musgraveData, x, y, z); + if (pixel.intensity > 1) { + pixel.intensity = 1.0f; + } else if (pixel.intensity < 0) { + pixel.intensity = 0.0f; + } + + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + pixel.alpha = colorBand[colorbandIndex][3]; + } else { + this.applyBrightnessAndContrast(bacd, pixel); + } + } - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - pixel.intensity = musgraveData.outscale * musgraveFunction.execute(musgraveData, x, y, z); - if(pixel.intensity>1) { - pixel.intensity = 1.0f; - } else if(pixel.intensity < 0) { - pixel.intensity = 0.0f; - } - - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - pixel.alpha = colorBand[colorbandIndex][3]; - } else { - this.applyBrightnessAndContrast(bacd, pixel); - } - } - protected static class MusgraveData { - public final int stype; - public final float outscale; - public final float h; - public final float lacunarity; - public final float octaves; - public final int noisebasis; - public final float offset; - public final float gain; - - public MusgraveData(Structure tex) { - stype = ((Number) tex.getFieldValue("stype")).intValue(); + public final int stype; + public final float outscale; + public final float h; + public final float lacunarity; + public final float octaves; + public final int noisebasis; + public final float offset; + public final float gain; + + public MusgraveData(Structure tex) { + stype = ((Number) tex.getFieldValue("stype")).intValue(); outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue(); h = ((Number) tex.getFieldValue("mg_H")).floatValue(); lacunarity = ((Number) tex.getFieldValue("mg_lacunarity")).floatValue(); @@ -109,6 +109,6 @@ public class TextureGeneratorMusgrave extends TextureGenerator { noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); offset = ((Number) tex.getFieldValue("mg_offset")).floatValue(); gain = ((Number) tex.getFieldValue("mg_gain")).floatValue(); - } + } } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorNoise.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorNoise.java index cffa74869..aa8b7b18d 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorNoise.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorNoise.java @@ -42,44 +42,44 @@ import com.jme3.texture.Image.Format; * @author Marcin Roguski (Kaelthas) */ public class TextureGeneratorNoise extends TextureGenerator { - protected int noisedepth; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorNoise(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - int random = FastMath.rand.nextInt(); - int val = random & 3; + protected int noisedepth; - int loop = noisedepth; - while (loop-- != 0) { - random >>= 2; - val *= random & 3; - } - pixel.intensity = FastMath.clamp(val, 0.0f, 1.0f); - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - - this.applyBrightnessAndContrast(bacd, pixel); - pixel.alpha = colorBand[colorbandIndex][3]; - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorNoise(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + int random = FastMath.rand.nextInt(); + int val = random & 3; + + int loop = noisedepth; + while (loop-- != 0) { + random >>= 2; + val *= random & 3; + } + pixel.intensity = FastMath.clamp(val, 0.0f, 1.0f); + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(bacd, pixel); + pixel.alpha = colorBand[colorbandIndex][3]; + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorStucci.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorStucci.java index 44bc72ee9..82b3237b5 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorStucci.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorStucci.java @@ -41,61 +41,61 @@ import com.jme3.texture.Image.Format; * @author Marcin Roguski (Kaelthas) */ public class TextureGeneratorStucci extends TextureGenerator { - protected static final int TEX_NOISESOFT = 0; - - protected float noisesize; - protected int noisebasis; - protected int noisetype; - protected float turbul; - protected boolean isHard; - protected int stype; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorStucci(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } + protected static final int TEX_NOISESOFT = 0; - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); - noisetype = ((Number) tex.getFieldValue("noisetype")).intValue(); - turbul = ((Number) tex.getFieldValue("turbul")).floatValue(); - isHard = noisetype != TEX_NOISESOFT; - stype = ((Number) tex.getFieldValue("stype")).intValue(); - if(noisesize<=0.001f) {//the texture goes black if this value is lower than 0.001f - noisesize = 0.001f; - } - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - float noiseValue = NoiseGenerator.NoiseFunctions.noise(x, y, z, noisesize, 0, noisebasis, isHard); - float ofs = turbul / 200.0f; - if (stype != 0) { - ofs *= noiseValue * noiseValue; - } + protected float noisesize; + protected int noisebasis; + protected int noisetype; + protected float turbul; + protected boolean isHard; + protected int stype; - pixel.intensity = NoiseGenerator.NoiseFunctions.noise(x, y, z + ofs, noisesize, 0, noisebasis, isHard); - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - pixel.alpha = colorBand[colorbandIndex][3]; - } + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorStucci(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } - if (stype == NoiseGenerator.TEX_WALLOUT) { - pixel.intensity = 1.0f - pixel.intensity; - } - if (pixel.intensity < 0.0f) { - pixel.intensity = 0.0f; - } - //no brightness and contrast needed for stucci (it doesn't affect the texture) - } + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + noisetype = ((Number) tex.getFieldValue("noisetype")).intValue(); + turbul = ((Number) tex.getFieldValue("turbul")).floatValue(); + isHard = noisetype != TEX_NOISESOFT; + stype = ((Number) tex.getFieldValue("stype")).intValue(); + if (noisesize <= 0.001f) {// the texture goes black if this value is lower than 0.001f + noisesize = 0.001f; + } + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + float noiseValue = NoiseGenerator.NoiseFunctions.noise(x, y, z, noisesize, 0, noisebasis, isHard); + float ofs = turbul / 200.0f; + if (stype != 0) { + ofs *= noiseValue * noiseValue; + } + + pixel.intensity = NoiseGenerator.NoiseFunctions.noise(x, y, z + ofs, noisesize, 0, noisebasis, isHard); + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + pixel.alpha = colorBand[colorbandIndex][3]; + } + + if (stype == NoiseGenerator.TEX_WALLOUT) { + pixel.intensity = 1.0f - pixel.intensity; + } + if (pixel.intensity < 0.0f) { + pixel.intensity = 0.0f; + } + // no brightness and contrast needed for stucci (it doesn't affect the texture) + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorVoronoi.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorVoronoi.java index 23a3e1633..e90eb0a33 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorVoronoi.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorVoronoi.java @@ -43,99 +43,99 @@ import com.jme3.texture.Image.Format; * @author Marcin Roguski (Kaelthas) */ public class TextureGeneratorVoronoi extends TextureGenerator { - protected float noisesize; - protected float outscale; - protected float mexp; - protected int distanceType; - protected int voronoiColorType; - protected float[] da = new float[4], pa = new float[12]; - protected float[] hashPoint; - protected float[] voronoiWeights; - protected float weightSum; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorVoronoi(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - voronoiWeights = new float[4]; - voronoiWeights[0] = ((Number) tex.getFieldValue("vn_w1")).floatValue(); - voronoiWeights[1] = ((Number) tex.getFieldValue("vn_w2")).floatValue(); - voronoiWeights[2] = ((Number) tex.getFieldValue("vn_w3")).floatValue(); - voronoiWeights[3] = ((Number) tex.getFieldValue("vn_w4")).floatValue(); - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue(); - mexp = ((Number) tex.getFieldValue("vn_mexp")).floatValue(); - distanceType = ((Number) tex.getFieldValue("vn_distm")).intValue(); - voronoiColorType = ((Number) tex.getFieldValue("vn_coltype")).intValue(); - hashPoint = voronoiColorType != 0 ? new float[3] : null; - weightSum = voronoiWeights[0] + voronoiWeights[1] + voronoiWeights[2] + voronoiWeights[3]; - if (weightSum != 0.0f) { - weightSum = outscale / weightSum; - } - if(voronoiColorType != 0 || colorBand != null) { - this.imageFormat = Format.RGBA8; - } - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - //for voronoi we need to widen the range a little - NoiseGenerator.NoiseFunctions.voronoi(x * 4, y * 4, z * 4, da, pa, mexp, distanceType); - pixel.intensity = weightSum * FastMath.abs(voronoiWeights[0] * da[0] + voronoiWeights[1] * da[1] + voronoiWeights[2] * da[2] + voronoiWeights[3] * da[3]); - if(pixel.intensity>1.0f) { - pixel.intensity = 1.0f; - } else if(pixel.intensity<0.0f) { - pixel.intensity = 0.0f; - } - - if (colorBand != null) {//colorband ALWAYS goes first and covers the color (if set) - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - pixel.alpha = colorBand[colorbandIndex][3]; - } else if (voronoiColorType != 0) { - pixel.red = pixel.green = pixel.blue = 0.0f; - pixel.alpha = 1.0f; - for(int m=0; m<12; m+=3) { - float weight = voronoiWeights[m/3]; - NoiseMath.hash((int)pa[m], (int)pa[m + 1], (int)pa[m + 2], hashPoint); - pixel.red += weight * hashPoint[0]; - pixel.green += weight * hashPoint[1]; - pixel.blue += weight * hashPoint[2]; - } - if (voronoiColorType >= 2) { - float t1 = (da[1] - da[0]) * 10.0f; - if (t1 > 1.0f) { - t1 = 1.0f; - } - if (voronoiColorType == 3) { - t1 *= pixel.intensity; - } else { - t1 *= weightSum; - } - pixel.red *= t1; - pixel.green *= t1; - pixel.blue *= t1; - } else { - pixel.red *= weightSum; - pixel.green *= weightSum; - pixel.blue *= weightSum; - } - } + protected float noisesize; + protected float outscale; + protected float mexp; + protected int distanceType; + protected int voronoiColorType; + protected float[] da = new float[4], pa = new float[12]; + protected float[] hashPoint; + protected float[] voronoiWeights; + protected float weightSum; - if (voronoiColorType != 0 || colorBand != null) { - this.applyBrightnessAndContrast(bacd, pixel); - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorVoronoi(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + voronoiWeights = new float[4]; + voronoiWeights[0] = ((Number) tex.getFieldValue("vn_w1")).floatValue(); + voronoiWeights[1] = ((Number) tex.getFieldValue("vn_w2")).floatValue(); + voronoiWeights[2] = ((Number) tex.getFieldValue("vn_w3")).floatValue(); + voronoiWeights[3] = ((Number) tex.getFieldValue("vn_w4")).floatValue(); + noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); + outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue(); + mexp = ((Number) tex.getFieldValue("vn_mexp")).floatValue(); + distanceType = ((Number) tex.getFieldValue("vn_distm")).intValue(); + voronoiColorType = ((Number) tex.getFieldValue("vn_coltype")).intValue(); + hashPoint = voronoiColorType != 0 ? new float[3] : null; + weightSum = voronoiWeights[0] + voronoiWeights[1] + voronoiWeights[2] + voronoiWeights[3]; + if (weightSum != 0.0f) { + weightSum = outscale / weightSum; + } + if (voronoiColorType != 0 || colorBand != null) { + this.imageFormat = Format.RGBA8; + } + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + // for voronoi we need to widen the range a little + NoiseGenerator.NoiseFunctions.voronoi(x * 4, y * 4, z * 4, da, pa, mexp, distanceType); + pixel.intensity = weightSum * FastMath.abs(voronoiWeights[0] * da[0] + voronoiWeights[1] * da[1] + voronoiWeights[2] * da[2] + voronoiWeights[3] * da[3]); + if (pixel.intensity > 1.0f) { + pixel.intensity = 1.0f; + } else if (pixel.intensity < 0.0f) { + pixel.intensity = 0.0f; + } + + if (colorBand != null) {// colorband ALWAYS goes first and covers the color (if set) + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + pixel.alpha = colorBand[colorbandIndex][3]; + } else if (voronoiColorType != 0) { + pixel.red = pixel.green = pixel.blue = 0.0f; + pixel.alpha = 1.0f; + for (int m = 0; m < 12; m += 3) { + float weight = voronoiWeights[m / 3]; + NoiseMath.hash((int) pa[m], (int) pa[m + 1], (int) pa[m + 2], hashPoint); + pixel.red += weight * hashPoint[0]; + pixel.green += weight * hashPoint[1]; + pixel.blue += weight * hashPoint[2]; + } + if (voronoiColorType >= 2) { + float t1 = (da[1] - da[0]) * 10.0f; + if (t1 > 1.0f) { + t1 = 1.0f; + } + if (voronoiColorType == 3) { + t1 *= pixel.intensity; + } else { + t1 *= weightSum; + } + pixel.red *= t1; + pixel.green *= t1; + pixel.blue *= t1; + } else { + pixel.red *= weightSum; + pixel.green *= weightSum; + pixel.blue *= weightSum; + } + } + + if (voronoiColorType != 0 || colorBand != null) { + this.applyBrightnessAndContrast(bacd, pixel); + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorWood.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorWood.java index a0f027b03..832dfbd9b 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorWood.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorWood.java @@ -41,54 +41,55 @@ import com.jme3.texture.Image.Format; * @author Marcin Roguski (Kaelthas) */ public class TextureGeneratorWood extends TextureGenerator { - // tex->noisebasis2 - protected static final int TEX_SIN = 0; - protected static final int TEX_SAW = 1; - protected static final int TEX_TRI = 2; - + // tex->noisebasis2 + protected static final int TEX_SIN = 0; + protected static final int TEX_SAW = 1; + protected static final int TEX_TRI = 2; + // tex->stype - protected static final int TEX_BAND = 0; - protected static final int TEX_RING = 1; - protected static final int TEX_BANDNOISE = 2; - protected static final int TEX_RINGNOISE = 3; - + protected static final int TEX_BAND = 0; + protected static final int TEX_RING = 1; + protected static final int TEX_BANDNOISE = 2; + protected static final int TEX_RINGNOISE = 3; + // tex->noisetype - protected static final int TEX_NOISESOFT = 0; - protected static final int TEX_NOISEPERL = 1; - + protected static final int TEX_NOISESOFT = 0; + protected static final int TEX_NOISEPERL = 1; + protected WoodIntensityData woodIntensityData; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator the noise generator - */ - public TextureGeneratorWood(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - woodIntensityData = new WoodIntensityData(tex); - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - pixel.intensity = this.woodIntensity(woodIntensityData, x, y, z); - - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - - this.applyBrightnessAndContrast(bacd, pixel); - pixel.alpha = colorBand[colorbandIndex][3]; - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } - + + /** + * Constructor stores the given noise generator. + * @param noiseGenerator + * the noise generator + */ + public TextureGeneratorWood(NoiseGenerator noiseGenerator) { + super(noiseGenerator, Format.Luminance8); + } + + @Override + public void readData(Structure tex, BlenderContext blenderContext) { + super.readData(tex, blenderContext); + woodIntensityData = new WoodIntensityData(tex); + } + + @Override + public void getPixel(TexturePixel pixel, float x, float y, float z) { + pixel.intensity = this.woodIntensity(woodIntensityData, x, y, z); + + if (colorBand != null) { + int colorbandIndex = (int) (pixel.intensity * 1000.0f); + pixel.red = colorBand[colorbandIndex][0]; + pixel.green = colorBand[colorbandIndex][1]; + pixel.blue = colorBand[colorbandIndex][2]; + + this.applyBrightnessAndContrast(bacd, pixel); + pixel.alpha = colorBand[colorbandIndex][3]; + } else { + this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); + } + } + protected static WaveForm[] waveformFunctions = new WaveForm[3]; static { waveformFunctions[0] = new WaveForm() {// sinus (TEX_SIN) @@ -119,63 +120,66 @@ public class TextureGeneratorWood extends TextureGenerator { /** * Computes basic wood intensity value at x,y,z. * @param woodIntData - * @param x X coordinate of the texture pixel - * @param y Y coordinate of the texture pixel - * @param z Z coordinate of the texture pixel + * @param x + * X coordinate of the texture pixel + * @param y + * Y coordinate of the texture pixel + * @param z + * Z coordinate of the texture pixel * @return wood intensity at position [x, y, z] */ public float woodIntensity(WoodIntensityData woodIntData, float x, float y, float z) { float result; - switch(woodIntData.woodType) { - case TEX_BAND: - result = woodIntData.waveformFunction.execute((x + y + z) * 10.0f); - break; - case TEX_RING: - result = woodIntData.waveformFunction.execute((float) Math.sqrt(x * x + y * y + z * z) * 20.0f); - break; - case TEX_BANDNOISE: - result = woodIntData.turbul * NoiseGenerator.NoiseFunctions.noise(x, y, z, woodIntData.noisesize, 0, woodIntData.noisebasis, woodIntData.isHard); - result = woodIntData.waveformFunction.execute((x + y + z) * 10.0f + result); - break; - case TEX_RINGNOISE: - result = woodIntData.turbul * NoiseGenerator.NoiseFunctions.noise(x, y, z, woodIntData.noisesize, 0, woodIntData.noisebasis, woodIntData.isHard); - result = woodIntData.waveformFunction.execute((float) Math.sqrt(x * x + y * y + z * z) * 20.0f + result); - break; - default: - result = 0; + switch (woodIntData.woodType) { + case TEX_BAND: + result = woodIntData.waveformFunction.execute((x + y + z) * 10.0f); + break; + case TEX_RING: + result = woodIntData.waveformFunction.execute((float) Math.sqrt(x * x + y * y + z * z) * 20.0f); + break; + case TEX_BANDNOISE: + result = woodIntData.turbul * NoiseGenerator.NoiseFunctions.noise(x, y, z, woodIntData.noisesize, 0, woodIntData.noisebasis, woodIntData.isHard); + result = woodIntData.waveformFunction.execute((x + y + z) * 10.0f + result); + break; + case TEX_RINGNOISE: + result = woodIntData.turbul * NoiseGenerator.NoiseFunctions.noise(x, y, z, woodIntData.noisesize, 0, woodIntData.noisebasis, woodIntData.isHard); + result = woodIntData.waveformFunction.execute((float) Math.sqrt(x * x + y * y + z * z) * 20.0f + result); + break; + default: + result = 0; } return result; } - + /** * A class that collects the data for wood intensity calculations. * @author Marcin Roguski (Kaelthas) */ private static class WoodIntensityData { - public final WaveForm waveformFunction; - public final int noisebasis; - public final float noisesize; - public final float turbul; - public final int noiseType; - public final int woodType; - public final boolean isHard; - - public WoodIntensityData(Structure tex) { - int waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue();//wave form: TEX_SIN=0, TEX_SAW=1, TEX_TRI=2 - if (waveform > TEX_TRI || waveform < TEX_SIN) { + public final WaveForm waveformFunction; + public final int noisebasis; + public final float noisesize; + public final float turbul; + public final int noiseType; + public final int woodType; + public final boolean isHard; + + public WoodIntensityData(Structure tex) { + int waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue();// wave form: TEX_SIN=0, TEX_SAW=1, TEX_TRI=2 + if (waveform > TEX_TRI || waveform < TEX_SIN) { waveform = 0; // check to be sure noisebasis2 is initialized ahead of time } - waveformFunction = waveformFunctions[waveform]; - noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); + waveformFunction = waveformFunctions[waveform]; + noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); woodType = ((Number) tex.getFieldValue("stype")).intValue(); noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); turbul = ((Number) tex.getFieldValue("turbul")).floatValue(); noiseType = ((Number) tex.getFieldValue("noisetype")).intValue(); isHard = noiseType != TEX_NOISESOFT; - } + } } - + protected static interface WaveForm { float execute(float x); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java index 93909db78..002c16746 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java @@ -10,154 +10,148 @@ import jme3tools.converters.RGB565; * Implemens read/write operations for AWT images. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class AWTPixelInputOutput implements PixelInputOutput { - public void read(Image image, int layer, TexturePixel pixel, int index) { - ByteBuffer data = image.getData(layer); - switch(image.getFormat()) { - case RGBA8: - pixel.fromARGB8(data.get(index + 3), data.get(index), data.get(index + 1), data.get(index + 2)); - break; - case ABGR8: - pixel.fromARGB8(data.get(index), data.get(index + 3), data.get(index + 2), data.get(index + 1)); - break; - case BGR8: - pixel.fromARGB8((byte)0xFF, data.get(index + 2), data.get(index + 1), data.get(index)); - break; - case RGB8: - pixel.fromARGB8((byte)0xFF, data.get(index), data.get(index + 1), data.get(index + 2)); - break; - case RGB565: - pixel.fromARGB8(RGB565.RGB565_to_ARGB8(data.getShort(index))); - break; - case RGB5A1: - short rgb5a1 = data.getShort(index); - byte a = (byte) (rgb5a1 & 0x01); - int r = (rgb5a1 & 0xf800) >> 11 << 3; - int g = (rgb5a1 & 0x07c0) >> 6 << 3; - int b = (rgb5a1 & 0x001f) >> 1 << 3; - pixel.fromARGB8(a == 1 ? (byte)255 : 0, (byte)r, (byte)g, (byte)b); - break; - case RGB16: - pixel.fromARGB16((short)0xFFFF, data.getShort(index), data.getShort(index + 2), data.getShort(index + 4)); - break; - case RGBA16: - pixel.fromARGB16(data.getShort(index + 6), data.getShort(index), data.getShort(index + 2), data.getShort(index + 4)); - break; - case RGB16F: - case RGB16F_to_RGB111110F: - case RGB16F_to_RGB9E5: - pixel.fromARGB(1, FastMath.convertHalfToFloat(data.getShort(index)), - FastMath.convertHalfToFloat(data.getShort(index + 2)), - FastMath.convertHalfToFloat(data.getShort(index + 4))); - break; - case RGBA16F: - pixel.fromARGB(FastMath.convertHalfToFloat(data.getShort(index + 6)), FastMath.convertHalfToFloat(data.getShort(index)), - FastMath.convertHalfToFloat(data.getShort(index + 2)), FastMath.convertHalfToFloat(data.getShort(index + 4))); - break; - case RGBA32F: - pixel.fromARGB(Float.intBitsToFloat(data.getInt(index + 12)), Float.intBitsToFloat(data.getInt(index)), - Float.intBitsToFloat(data.getInt(index + 4)), Float.intBitsToFloat(data.getInt(index + 8))); - break; - case RGB111110F://the data is stored as 32-bit unsigned int, that is why we cast the read data to long and remove MSB-bytes to get the positive value - pixel.fromARGB(1, (float)Double.longBitsToDouble((long)data.getInt(index) & 0x00000000FFFFFFFF), - (float)Double.longBitsToDouble((long)data.getInt(index + 4) & 0x00000000FFFFFFFF), - (float)Double.longBitsToDouble((long)data.getInt(index + 8) & 0x00000000FFFFFFFF)); - break; - case RGB10: - case RGB9E5://TODO: support these - throw new IllegalStateException("Not supported image type for IO operations: " + image.getFormat()); - default: - throw new IllegalStateException("Unknown image format: " + image.getFormat()); - } - } - - public void read(Image image, int layer, TexturePixel pixel, int x, int y) { - int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3); - this.read(image, layer, pixel, index); - } +/* package */class AWTPixelInputOutput implements PixelInputOutput { + public void read(Image image, int layer, TexturePixel pixel, int index) { + ByteBuffer data = image.getData(layer); + switch (image.getFormat()) { + case RGBA8: + pixel.fromARGB8(data.get(index + 3), data.get(index), data.get(index + 1), data.get(index + 2)); + break; + case ABGR8: + pixel.fromARGB8(data.get(index), data.get(index + 3), data.get(index + 2), data.get(index + 1)); + break; + case BGR8: + pixel.fromARGB8((byte) 0xFF, data.get(index + 2), data.get(index + 1), data.get(index)); + break; + case RGB8: + pixel.fromARGB8((byte) 0xFF, data.get(index), data.get(index + 1), data.get(index + 2)); + break; + case RGB565: + pixel.fromARGB8(RGB565.RGB565_to_ARGB8(data.getShort(index))); + break; + case RGB5A1: + short rgb5a1 = data.getShort(index); + byte a = (byte) (rgb5a1 & 0x01); + int r = (rgb5a1 & 0xf800) >> 11 << 3; + int g = (rgb5a1 & 0x07c0) >> 6 << 3; + int b = (rgb5a1 & 0x001f) >> 1 << 3; + pixel.fromARGB8(a == 1 ? (byte) 255 : 0, (byte) r, (byte) g, (byte) b); + break; + case RGB16: + pixel.fromARGB16((short) 0xFFFF, data.getShort(index), data.getShort(index + 2), data.getShort(index + 4)); + break; + case RGBA16: + pixel.fromARGB16(data.getShort(index + 6), data.getShort(index), data.getShort(index + 2), data.getShort(index + 4)); + break; + case RGB16F: + case RGB16F_to_RGB111110F: + case RGB16F_to_RGB9E5: + pixel.fromARGB(1, FastMath.convertHalfToFloat(data.getShort(index)), FastMath.convertHalfToFloat(data.getShort(index + 2)), FastMath.convertHalfToFloat(data.getShort(index + 4))); + break; + case RGBA16F: + pixel.fromARGB(FastMath.convertHalfToFloat(data.getShort(index + 6)), FastMath.convertHalfToFloat(data.getShort(index)), FastMath.convertHalfToFloat(data.getShort(index + 2)), FastMath.convertHalfToFloat(data.getShort(index + 4))); + break; + case RGBA32F: + pixel.fromARGB(Float.intBitsToFloat(data.getInt(index + 12)), Float.intBitsToFloat(data.getInt(index)), Float.intBitsToFloat(data.getInt(index + 4)), Float.intBitsToFloat(data.getInt(index + 8))); + break; + case RGB111110F:// the data is stored as 32-bit unsigned int, that is why we cast the read data to long and remove MSB-bytes to get the positive value + pixel.fromARGB(1, (float) Double.longBitsToDouble((long) data.getInt(index) & 0x00000000FFFFFFFF), (float) Double.longBitsToDouble((long) data.getInt(index + 4) & 0x00000000FFFFFFFF), (float) Double.longBitsToDouble((long) data.getInt(index + 8) & 0x00000000FFFFFFFF)); + break; + case RGB10: + case RGB9E5:// TODO: support these + throw new IllegalStateException("Not supported image type for IO operations: " + image.getFormat()); + default: + throw new IllegalStateException("Unknown image format: " + image.getFormat()); + } + } - public void write(Image image, int layer, TexturePixel pixel, int index) { - ByteBuffer data = image.getData(layer); - switch(image.getFormat()) { - case RGBA8: - data.put(index, pixel.getR8()); - data.put(index + 1, pixel.getG8()); - data.put(index + 2, pixel.getB8()); - data.put(index + 3, pixel.getA8()); - break; - case ABGR8: - data.put(index, pixel.getA8()); - data.put(index + 1, pixel.getB8()); - data.put(index + 2, pixel.getG8()); - data.put(index + 3, pixel.getR8()); - break; - case BGR8: - data.put(index, pixel.getB8()); - data.put(index + 1, pixel.getG8()); - data.put(index + 2, pixel.getR8()); - break; - case RGB8: - data.put(index, pixel.getR8()); - data.put(index + 1, pixel.getG8()); - data.put(index + 2, pixel.getB8()); - break; - case RGB565: - data.putShort(RGB565.ARGB8_to_RGB565(pixel.toARGB8())); - break; - case RGB5A1: - int argb8 = pixel.toARGB8(); - short r = (short) ((argb8 & 0x00F80000) >> 8); - short g = (short) ((argb8 & 0x0000F800) >> 5); - short b = (short) ((argb8 & 0x000000F8) >> 2); - short a = (short) ((short) ((argb8 & 0xFF000000) >> 24) > 0 ? 1 : 0); - data.putShort(index, (short) (r | g | b | a)); - break; - case RGB16: - data.putShort(index, pixel.getR16()); - data.putShort(index + 2, pixel.getG16()); - data.putShort(index + 4, pixel.getB16()); - break; - case RGBA16: - data.putShort(index, pixel.getR16()); - data.putShort(index + 2, pixel.getG16()); - data.putShort(index + 4, pixel.getB16()); - data.putShort(index + 6, pixel.getA16()); - break; - case RGB16F: - case RGB16F_to_RGB111110F: - case RGB16F_to_RGB9E5: - data.putShort(index, FastMath.convertFloatToHalf(pixel.red)); - data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.green)); - data.putShort(index + 4, FastMath.convertFloatToHalf(pixel.blue)); - break; - case RGBA16F: - data.putShort(index, FastMath.convertFloatToHalf(pixel.red)); - data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.green)); - data.putShort(index + 4, FastMath.convertFloatToHalf(pixel.blue)); - data.putShort(index + 6, FastMath.convertFloatToHalf(pixel.blue)); - break; - case RGB32F: - case RGB111110F://this data is stored as 32-bit unsigned int - data.putInt(index, Float.floatToIntBits(pixel.red)); - data.putInt(index + 2, Float.floatToIntBits(pixel.green)); - data.putInt(index + 4, Float.floatToIntBits(pixel.blue)); - break; - case RGBA32F: - data.putInt(index, Float.floatToIntBits(pixel.red)); - data.putInt(index + 2, Float.floatToIntBits(pixel.green)); - data.putInt(index + 4, Float.floatToIntBits(pixel.blue)); - data.putInt(index + 6, Float.floatToIntBits(pixel.alpha)); - break; - case RGB10: - case RGB9E5://TODO: support these - throw new IllegalStateException("Not supported image type for IO operations: " + image.getFormat()); - default: - throw new IllegalStateException("Unknown image format: " + image.getFormat()); - } - } - - public void write(Image image, int layer, TexturePixel pixel, int x, int y) { - int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3); - this.write(image, layer, pixel, index); - } + public void read(Image image, int layer, TexturePixel pixel, int x, int y) { + int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3); + this.read(image, layer, pixel, index); + } + + public void write(Image image, int layer, TexturePixel pixel, int index) { + ByteBuffer data = image.getData(layer); + switch (image.getFormat()) { + case RGBA8: + data.put(index, pixel.getR8()); + data.put(index + 1, pixel.getG8()); + data.put(index + 2, pixel.getB8()); + data.put(index + 3, pixel.getA8()); + break; + case ABGR8: + data.put(index, pixel.getA8()); + data.put(index + 1, pixel.getB8()); + data.put(index + 2, pixel.getG8()); + data.put(index + 3, pixel.getR8()); + break; + case BGR8: + data.put(index, pixel.getB8()); + data.put(index + 1, pixel.getG8()); + data.put(index + 2, pixel.getR8()); + break; + case RGB8: + data.put(index, pixel.getR8()); + data.put(index + 1, pixel.getG8()); + data.put(index + 2, pixel.getB8()); + break; + case RGB565: + data.putShort(RGB565.ARGB8_to_RGB565(pixel.toARGB8())); + break; + case RGB5A1: + int argb8 = pixel.toARGB8(); + short r = (short) ((argb8 & 0x00F80000) >> 8); + short g = (short) ((argb8 & 0x0000F800) >> 5); + short b = (short) ((argb8 & 0x000000F8) >> 2); + short a = (short) ((short) ((argb8 & 0xFF000000) >> 24) > 0 ? 1 : 0); + data.putShort(index, (short) (r | g | b | a)); + break; + case RGB16: + data.putShort(index, pixel.getR16()); + data.putShort(index + 2, pixel.getG16()); + data.putShort(index + 4, pixel.getB16()); + break; + case RGBA16: + data.putShort(index, pixel.getR16()); + data.putShort(index + 2, pixel.getG16()); + data.putShort(index + 4, pixel.getB16()); + data.putShort(index + 6, pixel.getA16()); + break; + case RGB16F: + case RGB16F_to_RGB111110F: + case RGB16F_to_RGB9E5: + data.putShort(index, FastMath.convertFloatToHalf(pixel.red)); + data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.green)); + data.putShort(index + 4, FastMath.convertFloatToHalf(pixel.blue)); + break; + case RGBA16F: + data.putShort(index, FastMath.convertFloatToHalf(pixel.red)); + data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.green)); + data.putShort(index + 4, FastMath.convertFloatToHalf(pixel.blue)); + data.putShort(index + 6, FastMath.convertFloatToHalf(pixel.blue)); + break; + case RGB32F: + case RGB111110F:// this data is stored as 32-bit unsigned int + data.putInt(index, Float.floatToIntBits(pixel.red)); + data.putInt(index + 2, Float.floatToIntBits(pixel.green)); + data.putInt(index + 4, Float.floatToIntBits(pixel.blue)); + break; + case RGBA32F: + data.putInt(index, Float.floatToIntBits(pixel.red)); + data.putInt(index + 2, Float.floatToIntBits(pixel.green)); + data.putInt(index + 4, Float.floatToIntBits(pixel.blue)); + data.putInt(index + 6, Float.floatToIntBits(pixel.alpha)); + break; + case RGB10: + case RGB9E5:// TODO: support these + throw new IllegalStateException("Not supported image type for IO operations: " + image.getFormat()); + default: + throw new IllegalStateException("Unknown image format: " + image.getFormat()); + } + } + + public void write(Image image, int layer, TexturePixel pixel, int x, int y) { + int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3); + this.write(image, layer, pixel, index); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/DDSPixelInputOutput.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/DDSPixelInputOutput.java index 6bcd62a1d..f83a062ab 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/DDSPixelInputOutput.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/DDSPixelInputOutput.java @@ -11,160 +11,160 @@ import jme3tools.converters.RGB565; * This class currently implements only read operation. * @author Marcin Roguski (Kaelthas) */ -/*package*/ class DDSPixelInputOutput implements PixelInputOutput { - /** - * For this class the index should be considered as a pixel index in AWT image format. - */ - public void read(Image image, int layer, TexturePixel pixel, int index) { - this.read(image, layer, pixel, index % image.getWidth(), index / image.getWidth()); - } - - public void read(Image image, int layer, TexturePixel pixel, int x, int y) { - int xTexetlIndex = x % image.getWidth() >> 2; - int yTexelIndex = y % image.getHeight() >> 2; - int xTexelCount = image.getWidth() >> 2; - int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex; - - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; - int indexes = 0; - long alphaIndexes = 0; - float[] alphas = null; - ByteBuffer data = image.getData().get(layer); - - switch (image.getFormat()) { - case DXT1: // BC1 - case DXT1A:{ - data.position(texelIndex * 8); - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - if (col0 > col1) { - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - } else { - // creating color2 = 1/2color0 + 1/2color1 - colors[2].fromPixel(colors[0]); - colors[2].add(colors[1]); - colors[2].mult(0.5f); - - colors[3].fromARGB8(0); - } - indexes = data.getInt();// 4-byte table with color indexes in decompressed table - break; - } - case DXT3: {// BC2 - data.position(texelIndex * 16); - long alpha = data.getLong(); - alphas = new float[16]; - for (int i = 0; i < 16; ++i) { - alphaIndexes |= i << i * 4; - byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4); - alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; - } - - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - - indexes = data.getInt();// 4-byte table with color indexes in decompressed table - break; - } - case DXT5: {// BC3 - data.position(texelIndex * 16); - alphas = new float[8]; - alphas[0] = data.get() * 255.0f; - alphas[1] = data.get() * 255.0f; - alphaIndexes = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40; - if (alphas[0] > alphas[1]) {// 6 interpolated alpha values. - alphas[2] = (6 * alphas[0] + alphas[1]) / 7; - alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7; - alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7; - alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7; - alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7; - alphas[7] = (alphas[0] + 6 * alphas[1]) / 7; - } else { - alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f; - alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f; - alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f; - alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f; - alphas[6] = 0; - alphas[7] = 1; - } - - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - - indexes = data.getInt();// 4-byte table with color indexes in decompressed table - break; - } - default: - throw new IllegalStateException("Unsupported decompression format."); - } - - // coordinates of the pixel in the selected texel - x = x - 4 * xTexetlIndex;// pixels are arranged from left to right - y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start) - - int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2); - int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0; - - // getting the pixel - int indexMask = colors.length - 1; - int colorIndex = indexes >> pixelIndexInTexel & indexMask; - float alpha = alphas != null ? alphas[(int) (alphaIndexes >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha; - pixel.fromPixel(colors[colorIndex]); - pixel.alpha = alpha; - } - - public void write(Image image, int layer, TexturePixel pixel, int index) { - throw new UnsupportedOperationException("Cannot put the DXT pixel by index because not every index contains the pixel color!"); - } - - public void write(Image image, int layer, TexturePixel pixel, int x, int y) { - throw new UnsupportedOperationException("Writing to DDS texture pixel by pixel is not yet supported!"); - } +/* package */class DDSPixelInputOutput implements PixelInputOutput { + /** + * For this class the index should be considered as a pixel index in AWT image format. + */ + public void read(Image image, int layer, TexturePixel pixel, int index) { + this.read(image, layer, pixel, index % image.getWidth(), index / image.getWidth()); + } + + public void read(Image image, int layer, TexturePixel pixel, int x, int y) { + int xTexetlIndex = x % image.getWidth() >> 2; + int yTexelIndex = y % image.getHeight() >> 2; + int xTexelCount = image.getWidth() >> 2; + int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex; + + TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; + int indexes = 0; + long alphaIndexes = 0; + float[] alphas = null; + ByteBuffer data = image.getData().get(layer); + + switch (image.getFormat()) { + case DXT1: // BC1 + case DXT1A: { + data.position(texelIndex * 8); + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + if (col0 > col1) { + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + } else { + // creating color2 = 1/2color0 + 1/2color1 + colors[2].fromPixel(colors[0]); + colors[2].add(colors[1]); + colors[2].mult(0.5f); + + colors[3].fromARGB8(0); + } + indexes = data.getInt();// 4-byte table with color indexes in decompressed table + break; + } + case DXT3: {// BC2 + data.position(texelIndex * 16); + long alpha = data.getLong(); + alphas = new float[16]; + for (int i = 0; i < 16; ++i) { + alphaIndexes |= i << i * 4; + byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4); + alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; + } + + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + + indexes = data.getInt();// 4-byte table with color indexes in decompressed table + break; + } + case DXT5: {// BC3 + data.position(texelIndex * 16); + alphas = new float[8]; + alphas[0] = data.get() * 255.0f; + alphas[1] = data.get() * 255.0f; + alphaIndexes = data.get() | data.get() << 8 | data.get() << 16 | data.get() << 24 | data.get() << 32 | data.get() << 40; + if (alphas[0] > alphas[1]) {// 6 interpolated alpha values. + alphas[2] = (6 * alphas[0] + alphas[1]) / 7; + alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7; + alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7; + alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7; + alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7; + alphas[7] = (alphas[0] + 6 * alphas[1]) / 7; + } else { + alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f; + alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f; + alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f; + alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f; + alphas[6] = 0; + alphas[7] = 1; + } + + short c0 = data.getShort(); + short c1 = data.getShort(); + int col0 = RGB565.RGB565_to_ARGB8(c0); + int col1 = RGB565.RGB565_to_ARGB8(c1); + colors[0].fromARGB8(col0); + colors[1].fromARGB8(col1); + + // creating color2 = 2/3color0 + 1/3color1 + colors[2].fromPixel(colors[0]); + colors[2].mult(2); + colors[2].add(colors[1]); + colors[2].divide(3); + + // creating color3 = 1/3color0 + 2/3color1; + colors[3].fromPixel(colors[1]); + colors[3].mult(2); + colors[3].add(colors[0]); + colors[3].divide(3); + + indexes = data.getInt();// 4-byte table with color indexes in decompressed table + break; + } + default: + throw new IllegalStateException("Unsupported decompression format."); + } + + // coordinates of the pixel in the selected texel + x = x - 4 * xTexetlIndex;// pixels are arranged from left to right + y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start) + + int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2); + int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0; + + // getting the pixel + int indexMask = colors.length - 1; + int colorIndex = indexes >> pixelIndexInTexel & indexMask; + float alpha = alphas != null ? alphas[(int) (alphaIndexes >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha; + pixel.fromPixel(colors[colorIndex]); + pixel.alpha = alpha; + } + + public void write(Image image, int layer, TexturePixel pixel, int index) { + throw new UnsupportedOperationException("Cannot put the DXT pixel by index because not every index contains the pixel color!"); + } + + public void write(Image image, int layer, TexturePixel pixel, int x, int y) { + throw new UnsupportedOperationException("Writing to DDS texture pixel by pixel is not yet supported!"); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/LuminancePixelInputOutput.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/LuminancePixelInputOutput.java index 4a6c41ae5..dc71f9e68 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/LuminancePixelInputOutput.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/LuminancePixelInputOutput.java @@ -11,78 +11,78 @@ import java.nio.ByteBuffer; * @author Marcin Roguski (Kaelthas) */ /* package */class LuminancePixelInputOutput implements PixelInputOutput { - public void read(Image image, int layer, TexturePixel pixel, int index) { - ByteBuffer data = image.getData(layer); - switch (image.getFormat()) { - case Luminance8: - pixel.fromIntensity(data.get(index)); - break; - case Luminance8Alpha8: - pixel.fromIntensity(data.get(index)); - pixel.setAlpha(data.get(index + 1)); - break; - case Luminance16: - pixel.fromIntensity(data.getShort(index)); - break; - case Luminance16Alpha16: - pixel.fromIntensity(data.getShort(index)); - pixel.setAlpha(data.getShort(index + 2)); - break; - case Luminance16F: - pixel.intensity = FastMath.convertHalfToFloat(data.getShort(index)); - break; - case Luminance16FAlpha16F: - pixel.intensity = FastMath.convertHalfToFloat(data.getShort(index)); - pixel.alpha = FastMath.convertHalfToFloat(data.getShort(index + 2)); - break; - case Luminance32F: - pixel.intensity = Float.intBitsToFloat(data.getInt(index)); - break; - default: - throw new IllegalStateException("Unknown luminance format type."); - } - } + public void read(Image image, int layer, TexturePixel pixel, int index) { + ByteBuffer data = image.getData(layer); + switch (image.getFormat()) { + case Luminance8: + pixel.fromIntensity(data.get(index)); + break; + case Luminance8Alpha8: + pixel.fromIntensity(data.get(index)); + pixel.setAlpha(data.get(index + 1)); + break; + case Luminance16: + pixel.fromIntensity(data.getShort(index)); + break; + case Luminance16Alpha16: + pixel.fromIntensity(data.getShort(index)); + pixel.setAlpha(data.getShort(index + 2)); + break; + case Luminance16F: + pixel.intensity = FastMath.convertHalfToFloat(data.getShort(index)); + break; + case Luminance16FAlpha16F: + pixel.intensity = FastMath.convertHalfToFloat(data.getShort(index)); + pixel.alpha = FastMath.convertHalfToFloat(data.getShort(index + 2)); + break; + case Luminance32F: + pixel.intensity = Float.intBitsToFloat(data.getInt(index)); + break; + default: + throw new IllegalStateException("Unknown luminance format type."); + } + } - public void read(Image image, int layer, TexturePixel pixel, int x, int y) { - int index = y * image.getWidth() + x; - this.read(image, layer, pixel, index); - } + public void read(Image image, int layer, TexturePixel pixel, int x, int y) { + int index = y * image.getWidth() + x; + this.read(image, layer, pixel, index); + } - public void write(Image image, int layer, TexturePixel pixel, int index) { - ByteBuffer data = image.getData(layer); - data.put(index, pixel.getInt()); - switch (image.getFormat()) { - case Luminance8: - data.put(index, pixel.getInt()); - break; - case Luminance8Alpha8: - data.put(index, pixel.getInt()); - data.put(index + 1, pixel.getA8()); - break; - case Luminance16: - data.putShort(index, (short) (pixel.intensity * 65535.0f)); - break; - case Luminance16Alpha16: - data.putShort(index, (short) (pixel.intensity * 65535.0f)); - data.putShort(index + 2, (short) (pixel.alpha * 65535.0f)); - break; - case Luminance16F: - data.putShort(index, FastMath.convertFloatToHalf(pixel.intensity)); - break; - case Luminance16FAlpha16F: - data.putShort(index, FastMath.convertFloatToHalf(pixel.intensity)); - data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.alpha)); - break; - case Luminance32F: - data.putInt(index, Float.floatToIntBits(pixel.intensity)); - break; - default: - throw new IllegalStateException("Unknown luminance format type."); - } - } + public void write(Image image, int layer, TexturePixel pixel, int index) { + ByteBuffer data = image.getData(layer); + data.put(index, pixel.getInt()); + switch (image.getFormat()) { + case Luminance8: + data.put(index, pixel.getInt()); + break; + case Luminance8Alpha8: + data.put(index, pixel.getInt()); + data.put(index + 1, pixel.getA8()); + break; + case Luminance16: + data.putShort(index, (short) (pixel.intensity * 65535.0f)); + break; + case Luminance16Alpha16: + data.putShort(index, (short) (pixel.intensity * 65535.0f)); + data.putShort(index + 2, (short) (pixel.alpha * 65535.0f)); + break; + case Luminance16F: + data.putShort(index, FastMath.convertFloatToHalf(pixel.intensity)); + break; + case Luminance16FAlpha16F: + data.putShort(index, FastMath.convertFloatToHalf(pixel.intensity)); + data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.alpha)); + break; + case Luminance32F: + data.putInt(index, Float.floatToIntBits(pixel.intensity)); + break; + default: + throw new IllegalStateException("Unknown luminance format type."); + } + } - public void write(Image image, int layer, TexturePixel pixel, int x, int y) { - int index = y * image.getWidth() + x; - this.write(image, layer, pixel, index); - } + public void write(Image image, int layer, TexturePixel pixel, int x, int y) { + int index = y * image.getWidth() + x; + this.write(image, layer, pixel, index); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java index ebd3b492c..3ef284e8a 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java @@ -10,60 +10,60 @@ import java.util.Map; * @author Marcin Roguski (Kaelthas) */ public class PixelIOFactory { - private static final Map PIXEL_INPUT_OUTPUT = new HashMap(); + private static final Map PIXEL_INPUT_OUTPUT = new HashMap(); - /** - * This method returns pixel IO object for the specified format. - * - * @param format - * the format of the image - * @return pixel IO object - */ - public static PixelInputOutput getPixelIO(Format format) { - PixelInputOutput result = PIXEL_INPUT_OUTPUT.get(format); - if (result == null) { - switch (format) { - case ABGR8: - case RGBA8: - case BGR8: - case RGB8: - case RGB10: - case RGB111110F: - case RGB16: - case RGB16F: - case RGB16F_to_RGB111110F: - case RGB16F_to_RGB9E5: - case RGB32F: - case RGB565: - case RGB5A1: - case RGB9E5: - case RGBA16: - case RGBA16F: - case RGBA32F: - result = new AWTPixelInputOutput(); - break; - case Luminance8: - case Luminance16: - case Luminance16Alpha16: - case Luminance16F: - case Luminance16FAlpha16F: - case Luminance32F: - case Luminance8Alpha8: - result = new LuminancePixelInputOutput(); - break; - case DXT1: - case DXT1A: - case DXT3: - case DXT5: - result = new DDSPixelInputOutput(); - break; - default: - throw new IllegalStateException("Unsupported image format for IO operations: " + format); - } - synchronized (PIXEL_INPUT_OUTPUT) { - PIXEL_INPUT_OUTPUT.put(format, result); - } - } - return result; - } + /** + * This method returns pixel IO object for the specified format. + * + * @param format + * the format of the image + * @return pixel IO object + */ + public static PixelInputOutput getPixelIO(Format format) { + PixelInputOutput result = PIXEL_INPUT_OUTPUT.get(format); + if (result == null) { + switch (format) { + case ABGR8: + case RGBA8: + case BGR8: + case RGB8: + case RGB10: + case RGB111110F: + case RGB16: + case RGB16F: + case RGB16F_to_RGB111110F: + case RGB16F_to_RGB9E5: + case RGB32F: + case RGB565: + case RGB5A1: + case RGB9E5: + case RGBA16: + case RGBA16F: + case RGBA32F: + result = new AWTPixelInputOutput(); + break; + case Luminance8: + case Luminance16: + case Luminance16Alpha16: + case Luminance16F: + case Luminance16FAlpha16F: + case Luminance32F: + case Luminance8Alpha8: + result = new LuminancePixelInputOutput(); + break; + case DXT1: + case DXT1A: + case DXT3: + case DXT5: + result = new DDSPixelInputOutput(); + break; + default: + throw new IllegalStateException("Unsupported image format for IO operations: " + format); + } + synchronized (PIXEL_INPUT_OUTPUT) { + PIXEL_INPUT_OUTPUT.put(format, result); + } + } + return result; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/PixelInputOutput.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/PixelInputOutput.java index 3eb83945b..3ff592047 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/PixelInputOutput.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/io/PixelInputOutput.java @@ -9,57 +9,57 @@ import com.jme3.texture.Image; * @author Marcin Roguski (Kaelthas) */ public interface PixelInputOutput { - /** - * This method reads a pixel that starts at the given index. - * - * @param image - * the image we read pixel from - * @param pixel - * the pixel where the result is stored - * @param index - * the index where the pixel begins in the image data - */ - void read(Image image, int layer, TexturePixel pixel, int index); + /** + * This method reads a pixel that starts at the given index. + * + * @param image + * the image we read pixel from + * @param pixel + * the pixel where the result is stored + * @param index + * the index where the pixel begins in the image data + */ + void read(Image image, int layer, TexturePixel pixel, int index); - /** - * This method reads a pixel that is located at the given position on the - * image. - * - * @param image - * the image we read pixel from - * @param pixel - * the pixel where the result is stored - * @param x - * the X coordinate of the pixel - * @param y - * the Y coordinate of the pixel - */ - void read(Image image, int layer, TexturePixel pixel, int x, int y); + /** + * This method reads a pixel that is located at the given position on the + * image. + * + * @param image + * the image we read pixel from + * @param pixel + * the pixel where the result is stored + * @param x + * the X coordinate of the pixel + * @param y + * the Y coordinate of the pixel + */ + void read(Image image, int layer, TexturePixel pixel, int x, int y); - /** - * This method writes a pixel that starts at the given index. - * - * @param image - * the image we read pixel from - * @param pixel - * the pixel where the result is stored - * @param index - * the index where the pixel begins in the image data - */ - void write(Image image, int layer, TexturePixel pixel, int index); + /** + * This method writes a pixel that starts at the given index. + * + * @param image + * the image we read pixel from + * @param pixel + * the pixel where the result is stored + * @param index + * the index where the pixel begins in the image data + */ + void write(Image image, int layer, TexturePixel pixel, int index); - /** - * This method writes a pixel that is located at the given position on the - * image. - * - * @param image - * the image we read pixel from - * @param pixel - * the pixel where the result is stored - * @param x - * the X coordinate of the pixel - * @param y - * the Y coordinate of the pixel - */ - void write(Image image, int layer, TexturePixel pixel, int x, int y); + /** + * This method writes a pixel that is located at the given position on the + * image. + * + * @param image + * the image we read pixel from + * @param pixel + * the pixel where the result is stored + * @param x + * the X coordinate of the pixel + * @param y + * the Y coordinate of the pixel + */ + void write(Image image, int layer, TexturePixel pixel, int x, int y); }