Feature: added sky loading.
git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10811 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
This commit is contained in:
parent
592303181e
commit
eb7e7bbaad
@ -46,9 +46,9 @@ import com.jme3.export.InputCapsule;
|
|||||||
import com.jme3.export.JmeExporter;
|
import com.jme3.export.JmeExporter;
|
||||||
import com.jme3.export.JmeImporter;
|
import com.jme3.export.JmeImporter;
|
||||||
import com.jme3.export.OutputCapsule;
|
import com.jme3.export.OutputCapsule;
|
||||||
import com.jme3.light.AmbientLight;
|
|
||||||
import com.jme3.material.Material;
|
import com.jme3.material.Material;
|
||||||
import com.jme3.material.RenderState.FaceCullMode;
|
import com.jme3.material.RenderState.FaceCullMode;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
import com.jme3.scene.CameraNode;
|
import com.jme3.scene.CameraNode;
|
||||||
import com.jme3.scene.LightNode;
|
import com.jme3.scene.LightNode;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
@ -63,24 +63,24 @@ import com.jme3.texture.Texture;
|
|||||||
*/
|
*/
|
||||||
public class BlenderKey extends ModelKey {
|
public class BlenderKey extends ModelKey {
|
||||||
|
|
||||||
protected static final int DEFAULT_FPS = 25;
|
protected static final int DEFAULT_FPS = 25;
|
||||||
/**
|
/**
|
||||||
* FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
|
* FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
|
||||||
* between the frames.
|
* between the frames.
|
||||||
*/
|
*/
|
||||||
protected int fps = DEFAULT_FPS;
|
protected int fps = DEFAULT_FPS;
|
||||||
/**
|
/**
|
||||||
* This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
|
* This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
|
||||||
*/
|
*/
|
||||||
protected int featuresToLoad = FeaturesToLoad.ALL;
|
protected int featuresToLoad = FeaturesToLoad.ALL;
|
||||||
/** This variable determines if assets that are not linked to the objects should be loaded. */
|
/** This variable determines if assets that are not linked to the objects should be loaded. */
|
||||||
protected boolean loadUnlinkedAssets;
|
protected boolean loadUnlinkedAssets;
|
||||||
/** The root path for all the assets. */
|
/** The root path for all the assets. */
|
||||||
protected String assetRootPath;
|
protected String assetRootPath;
|
||||||
/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */
|
/** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */
|
||||||
protected boolean fixUpAxis = true;
|
protected boolean fixUpAxis = true;
|
||||||
/** Generated textures resolution (PPU - Pixels Per Unit). */
|
/** Generated textures resolution (PPU - Pixels Per Unit). */
|
||||||
protected int generatedTexturePPU = 128;
|
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
|
* 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.
|
* then the first world settings in the file will be used.
|
||||||
@ -92,20 +92,25 @@ public class BlenderKey extends ModelKey {
|
|||||||
*/
|
*/
|
||||||
protected Material defaultMaterial;
|
protected Material defaultMaterial;
|
||||||
/** Face cull mode. By default it is disabled. */
|
/** Face cull mode. By default it is disabled. */
|
||||||
protected FaceCullMode faceCullMode = FaceCullMode.Back;
|
protected FaceCullMode faceCullMode = FaceCullMode.Back;
|
||||||
/**
|
/**
|
||||||
* Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded.
|
* 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.
|
* If set to -1 then the current layer will be loaded.
|
||||||
*/
|
*/
|
||||||
protected int layersToLoad = -1;
|
protected int layersToLoad = -1;
|
||||||
/** A variable that toggles the object custom properties loading. */
|
/** A variable that toggles the object custom properties loading. */
|
||||||
protected boolean loadObjectProperties = true;
|
protected boolean loadObjectProperties = true;
|
||||||
/** Maximum texture size. Might be dependant on the graphic card. */
|
/** Maximum texture size. Might be dependant on the graphic card. */
|
||||||
protected int maxTextureSize = -1;
|
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. */
|
/** 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;
|
protected boolean loadGeneratedTextures;
|
||||||
/** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */
|
/** 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;
|
protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED;
|
||||||
|
/**
|
||||||
|
* If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated
|
||||||
|
* textures will get their proper size.
|
||||||
|
*/
|
||||||
|
protected int skyGeneratedTextureSize = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor used by serialization mechanisms.
|
* Constructor used by serialization mechanisms.
|
||||||
@ -356,6 +361,24 @@ public class BlenderKey extends ModelKey {
|
|||||||
this.mipmapGenerationMethod = mipmapGenerationMethod;
|
this.mipmapGenerationMethod = mipmapGenerationMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the size of the generated textures for the sky (used if no flat textures are applied)
|
||||||
|
*/
|
||||||
|
public int getSkyGeneratedTextureSize() {
|
||||||
|
return skyGeneratedTextureSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param skyGeneratedTextureSize
|
||||||
|
* the size of the generated textures for the sky (used if no flat textures are applied)
|
||||||
|
*/
|
||||||
|
public void setSkyGeneratedTextureSize(int skyGeneratedTextureSize) {
|
||||||
|
if (skyGeneratedTextureSize <= 0) {
|
||||||
|
throw new IllegalArgumentException("The texture size must be a positive value (the value given as a parameter: " + skyGeneratedTextureSize + ")!");
|
||||||
|
}
|
||||||
|
this.skyGeneratedTextureSize = skyGeneratedTextureSize;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is
|
* 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
|
* 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
|
||||||
@ -521,6 +544,7 @@ public class BlenderKey extends ModelKey {
|
|||||||
int TEXTURES = 0x00000001;
|
int TEXTURES = 0x00000001;
|
||||||
int CAMERAS = 0x00000020;
|
int CAMERAS = 0x00000020;
|
||||||
int LIGHTS = 0x00000010;
|
int LIGHTS = 0x00000010;
|
||||||
|
int WORLD = 0x00000040;
|
||||||
int ALL = 0xFFFFFFFF;
|
int ALL = 0xFFFFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,21 +555,28 @@ public class BlenderKey extends ModelKey {
|
|||||||
public static class LoadingResults extends Spatial {
|
public static class LoadingResults extends Spatial {
|
||||||
|
|
||||||
/** Bitwise mask of features that are to be loaded. */
|
/** Bitwise mask of features that are to be loaded. */
|
||||||
private final int featuresToLoad;
|
private final int featuresToLoad;
|
||||||
/** The scenes from the file. */
|
/** The scenes from the file. */
|
||||||
private List<Node> scenes;
|
private List<Node> scenes;
|
||||||
/** Objects from all scenes. */
|
/** Objects from all scenes. */
|
||||||
private List<Node> objects;
|
private List<Node> objects;
|
||||||
/** Materials from all objects. */
|
/** Materials from all objects. */
|
||||||
private List<Material> materials;
|
private List<Material> materials;
|
||||||
/** Textures from all objects. */
|
/** Textures from all objects. */
|
||||||
private List<Texture> textures;
|
private List<Texture> textures;
|
||||||
/** Animations of all objects. */
|
/** Animations of all objects. */
|
||||||
private List<AnimationData> animations;
|
private List<AnimationData> animations;
|
||||||
/** All cameras from the file. */
|
/** All cameras from the file. */
|
||||||
private List<CameraNode> cameras;
|
private List<CameraNode> cameras;
|
||||||
/** All lights from the file. */
|
/** All lights from the file. */
|
||||||
private List<LightNode> lights;
|
private List<LightNode> lights;
|
||||||
|
/** Loaded sky. */
|
||||||
|
private Spatial sky;
|
||||||
|
/**
|
||||||
|
* The background color of the render loaded from the horizon color of the world. If no world is used than the gray color
|
||||||
|
* is set to default (as in blender editor.
|
||||||
|
*/
|
||||||
|
private ColorRGBA backgroundColor = ColorRGBA.Gray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Private constructor prevents users to create an instance of this class from outside the
|
* Private constructor prevents users to create an instance of this class from outside the
|
||||||
@ -654,7 +685,23 @@ public class BlenderKey extends ModelKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns all loaded scenes.
|
* This method sets the sky of the scene. Only one sky can be set.
|
||||||
|
* @param sky
|
||||||
|
* the sky to be set
|
||||||
|
*/
|
||||||
|
public void setSky(Spatial sky) {
|
||||||
|
this.sky = sky;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param backgroundColor
|
||||||
|
* the background color
|
||||||
|
*/
|
||||||
|
public void setBackgroundColor(ColorRGBA backgroundColor) {
|
||||||
|
this.backgroundColor = backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
* @return all loaded scenes
|
* @return all loaded scenes
|
||||||
*/
|
*/
|
||||||
public List<Node> getScenes() {
|
public List<Node> getScenes() {
|
||||||
@ -662,7 +709,6 @@ public class BlenderKey extends ModelKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns all loaded objects.
|
|
||||||
* @return all loaded objects
|
* @return all loaded objects
|
||||||
*/
|
*/
|
||||||
public List<Node> getObjects() {
|
public List<Node> getObjects() {
|
||||||
@ -670,7 +716,6 @@ public class BlenderKey extends ModelKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns all loaded materials.
|
|
||||||
* @return all loaded materials
|
* @return all loaded materials
|
||||||
*/
|
*/
|
||||||
public List<Material> getMaterials() {
|
public List<Material> getMaterials() {
|
||||||
@ -678,7 +723,6 @@ public class BlenderKey extends ModelKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns all loaded textures.
|
|
||||||
* @return all loaded textures
|
* @return all loaded textures
|
||||||
*/
|
*/
|
||||||
public List<Texture> getTextures() {
|
public List<Texture> getTextures() {
|
||||||
@ -686,7 +730,6 @@ public class BlenderKey extends ModelKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns all loaded animations.
|
|
||||||
* @return all loaded animations
|
* @return all loaded animations
|
||||||
*/
|
*/
|
||||||
public List<AnimationData> getAnimations() {
|
public List<AnimationData> getAnimations() {
|
||||||
@ -694,7 +737,6 @@ public class BlenderKey extends ModelKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns all loaded cameras.
|
|
||||||
* @return all loaded cameras
|
* @return all loaded cameras
|
||||||
*/
|
*/
|
||||||
public List<CameraNode> getCameras() {
|
public List<CameraNode> getCameras() {
|
||||||
@ -702,13 +744,26 @@ public class BlenderKey extends ModelKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method returns all loaded lights.
|
|
||||||
* @return all loaded lights
|
* @return all loaded lights
|
||||||
*/
|
*/
|
||||||
public List<LightNode> getLights() {
|
public List<LightNode> getLights() {
|
||||||
return lights;
|
return lights;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the scene's sky
|
||||||
|
*/
|
||||||
|
public Spatial getSky() {
|
||||||
|
return sky;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the background color
|
||||||
|
*/
|
||||||
|
public ColorRGBA getBackgroundColor() {
|
||||||
|
return backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {
|
public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -744,32 +799,4 @@ public class BlenderKey extends ModelKey {
|
|||||||
protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
|
protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The WORLD file block contains various data that could be added to the scene. The contained data includes: ambient
|
|
||||||
* light.
|
|
||||||
* @author Marcin Roguski (Kaelthas)
|
|
||||||
*/
|
|
||||||
public static class WorldData {
|
|
||||||
|
|
||||||
/** The ambient light. */
|
|
||||||
private AmbientLight ambientLight;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method returns the world's ambient light.
|
|
||||||
* @return the world's ambient light
|
|
||||||
*/
|
|
||||||
public AmbientLight getAmbientLight() {
|
|
||||||
return ambientLight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method sets the world's ambient light.
|
|
||||||
* @param ambientLight
|
|
||||||
* the world's ambient light
|
|
||||||
*/
|
|
||||||
public void setAmbientLight(AmbientLight ambientLight) {
|
|
||||||
this.ambientLight = ambientLight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -37,9 +37,6 @@ import java.util.logging.Logger;
|
|||||||
|
|
||||||
import com.jme3.asset.AssetLoader;
|
import com.jme3.asset.AssetLoader;
|
||||||
import com.jme3.asset.BlenderKey.FeaturesToLoad;
|
import com.jme3.asset.BlenderKey.FeaturesToLoad;
|
||||||
import com.jme3.asset.BlenderKey.WorldData;
|
|
||||||
import com.jme3.light.AmbientLight;
|
|
||||||
import com.jme3.math.ColorRGBA;
|
|
||||||
import com.jme3.scene.CameraNode;
|
import com.jme3.scene.CameraNode;
|
||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.LightNode;
|
import com.jme3.scene.LightNode;
|
||||||
@ -166,25 +163,4 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
|
|||||||
// }
|
// }
|
||||||
// return null;
|
// 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();
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,6 @@ import com.jme3.asset.AssetInfo;
|
|||||||
import com.jme3.asset.BlenderKey;
|
import com.jme3.asset.BlenderKey;
|
||||||
import com.jme3.asset.BlenderKey.FeaturesToLoad;
|
import com.jme3.asset.BlenderKey.FeaturesToLoad;
|
||||||
import com.jme3.asset.BlenderKey.LoadingResults;
|
import com.jme3.asset.BlenderKey.LoadingResults;
|
||||||
import com.jme3.asset.BlenderKey.WorldData;
|
|
||||||
import com.jme3.asset.ModelKey;
|
import com.jme3.asset.ModelKey;
|
||||||
import com.jme3.scene.CameraNode;
|
import com.jme3.scene.CameraNode;
|
||||||
import com.jme3.scene.LightNode;
|
import com.jme3.scene.LightNode;
|
||||||
@ -56,6 +55,7 @@ import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
|||||||
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
import com.jme3.scene.plugins.blender.file.BlenderInputStream;
|
||||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
||||||
import com.jme3.scene.plugins.blender.file.Structure;
|
import com.jme3.scene.plugins.blender.file.Structure;
|
||||||
|
import com.jme3.scene.plugins.blender.landscape.LandscapeHelper;
|
||||||
import com.jme3.scene.plugins.blender.lights.LightHelper;
|
import com.jme3.scene.plugins.blender.lights.LightHelper;
|
||||||
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
|
import com.jme3.scene.plugins.blender.materials.MaterialHelper;
|
||||||
import com.jme3.scene.plugins.blender.meshes.MeshHelper;
|
import com.jme3.scene.plugins.blender.meshes.MeshHelper;
|
||||||
@ -82,7 +82,6 @@ public class BlenderLoader extends AbstractBlenderLoader {
|
|||||||
List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
|
List<FileBlockHeader> sceneBlocks = new ArrayList<FileBlockHeader>();
|
||||||
BlenderKey blenderKey = blenderContext.getBlenderKey();
|
BlenderKey blenderKey = blenderContext.getBlenderKey();
|
||||||
LoadingResults loadingResults = blenderKey.prepareLoadingResults();
|
LoadingResults loadingResults = blenderKey.prepareLoadingResults();
|
||||||
WorldData worldData = null;// a set of data used in different scene aspects
|
|
||||||
for (FileBlockHeader block : blocks) {
|
for (FileBlockHeader block : blocks) {
|
||||||
switch (block.getCode()) {
|
switch (block.getCode()) {
|
||||||
case FileBlockHeader.BLOCK_OB00:// Object
|
case FileBlockHeader.BLOCK_OB00:// Object
|
||||||
@ -109,14 +108,14 @@ public class BlenderLoader extends AbstractBlenderLoader {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case FileBlockHeader.BLOCK_WO00:// World
|
case FileBlockHeader.BLOCK_WO00:// World
|
||||||
if (blenderKey.isLoadUnlinkedAssets() && worldData == null) {// onlu one world data is used
|
if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.WORLD) != 0) {
|
||||||
Structure worldStructure = block.getStructure(blenderContext);
|
Structure worldStructure = block.getStructure(blenderContext);
|
||||||
String worldName = worldStructure.getName();
|
String worldName = worldStructure.getName();
|
||||||
if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
|
if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) {
|
||||||
worldData = this.toWorldData(worldStructure);
|
LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class);
|
||||||
if ((blenderKey.getFeaturesToLoad() & FeaturesToLoad.LIGHTS) != 0) {
|
loadingResults.addLight(landscapeHelper.toAmbientLight(worldStructure));
|
||||||
loadingResults.addLight(worldData.getAmbientLight());
|
loadingResults.setSky(landscapeHelper.toSky(worldStructure));
|
||||||
}
|
loadingResults.setBackgroundColor(landscapeHelper.toBackgroundColor(worldStructure));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -206,6 +205,7 @@ public class BlenderLoader extends AbstractBlenderLoader {
|
|||||||
blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext));
|
blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext));
|
||||||
blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderContext));
|
blenderContext.putHelper(IpoHelper.class, new IpoHelper(inputStream.getVersionNumber(), blenderContext));
|
||||||
blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext));
|
blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext));
|
||||||
|
blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext));
|
||||||
|
|
||||||
// reading the blocks (dna block is automatically saved in the blender context when found)
|
// reading the blocks (dna block is automatically saved in the blender context when found)
|
||||||
FileBlockHeader sceneFileBlock = null;
|
FileBlockHeader sceneFileBlock = null;
|
||||||
|
@ -0,0 +1,184 @@
|
|||||||
|
package com.jme3.scene.plugins.blender.landscape;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import com.jme3.light.AmbientLight;
|
||||||
|
import com.jme3.light.Light;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.scene.Spatial;
|
||||||
|
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||||
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
|
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||||
|
import com.jme3.scene.plugins.blender.file.Structure;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.ColorBand;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.CombinedTexture;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.ImageUtils;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.TextureHelper;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.TexturePixel;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
|
||||||
|
import com.jme3.texture.Image;
|
||||||
|
import com.jme3.texture.Image.Format;
|
||||||
|
import com.jme3.texture.TextureCubeMap;
|
||||||
|
import com.jme3.util.SkyFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class that allows to load the following: <li>the ambient light of the scene <li>the sky of the scene (with or without texture)
|
||||||
|
*
|
||||||
|
* @author Marcin Roguski (Kaelthas)
|
||||||
|
*/
|
||||||
|
public class LandscapeHelper extends AbstractBlenderHelper {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(LandscapeHelper.class.getName());
|
||||||
|
|
||||||
|
private static final int SKYTYPE_BLEND = 1;
|
||||||
|
private static final int SKYTYPE_REAL = 2;
|
||||||
|
private static final int SKYTYPE_PAPER = 4;
|
||||||
|
|
||||||
|
public LandscapeHelper(String blenderVersion, BlenderContext blenderContext) {
|
||||||
|
super(blenderVersion, blenderContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads scene ambient light.
|
||||||
|
* @param worldStructure
|
||||||
|
* the world's blender structure
|
||||||
|
* @return the scene's ambient light
|
||||||
|
*/
|
||||||
|
public Light toAmbientLight(Structure worldStructure) {
|
||||||
|
LOGGER.fine("Loading ambient light.");
|
||||||
|
AmbientLight ambientLight = new AmbientLight();
|
||||||
|
float ambr = ((Number) worldStructure.getFieldValue("ambr")).floatValue();
|
||||||
|
float ambg = ((Number) worldStructure.getFieldValue("ambg")).floatValue();
|
||||||
|
float ambb = ((Number) worldStructure.getFieldValue("ambb")).floatValue();
|
||||||
|
ColorRGBA ambientLightColor = new ColorRGBA(ambr, ambg, ambb, 0.0f);
|
||||||
|
ambientLight.setColor(ambientLightColor);
|
||||||
|
LOGGER.log(Level.FINE, "Loaded ambient light: {0}.", ambientLightColor);
|
||||||
|
return ambientLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the background color.
|
||||||
|
* @param worldStructure
|
||||||
|
* the world's structure
|
||||||
|
* @return the horizon color of the world which is used as a background color.
|
||||||
|
*/
|
||||||
|
public ColorRGBA toBackgroundColor(Structure worldStructure) {
|
||||||
|
float horr = ((Number) worldStructure.getFieldValue("horr")).floatValue();
|
||||||
|
float horg = ((Number) worldStructure.getFieldValue("horg")).floatValue();
|
||||||
|
float horb = ((Number) worldStructure.getFieldValue("horb")).floatValue();
|
||||||
|
return new ColorRGBA(horr, horg, horb, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads scene's sky. Sky can be plain or textured.
|
||||||
|
* If no sky type is selected in blender then no sky is loaded.
|
||||||
|
* @param worldStructure
|
||||||
|
* the world's structure
|
||||||
|
* @return the scene's sky
|
||||||
|
* @throws BlenderFileException
|
||||||
|
* blender exception is thrown when problems with blender file occur
|
||||||
|
*/
|
||||||
|
public Spatial toSky(Structure worldStructure) throws BlenderFileException {
|
||||||
|
int skytype = ((Number) worldStructure.getFieldValue("skytype")).intValue();
|
||||||
|
if (skytype == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.fine("Loading sky.");
|
||||||
|
ColorRGBA horizontalColor = this.toBackgroundColor(worldStructure);
|
||||||
|
|
||||||
|
float zenr = ((Number) worldStructure.getFieldValue("zenr")).floatValue();
|
||||||
|
float zeng = ((Number) worldStructure.getFieldValue("zeng")).floatValue();
|
||||||
|
float zenb = ((Number) worldStructure.getFieldValue("zenb")).floatValue();
|
||||||
|
ColorRGBA zenithColor = new ColorRGBA(zenr, zeng, zenb, 1);
|
||||||
|
|
||||||
|
// jutr for this case load generated textures wheather user had set it or not because those might be needed to properly load the sky
|
||||||
|
boolean loadGeneratedTextures = blenderContext.getBlenderKey().isLoadGeneratedTextures();
|
||||||
|
blenderContext.getBlenderKey().setLoadGeneratedTextures(true);
|
||||||
|
|
||||||
|
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
|
||||||
|
Map<Number, CombinedTexture> loadedTextures = null;
|
||||||
|
try {
|
||||||
|
loadedTextures = textureHelper.readTextureData(worldStructure, new float[] { horizontalColor.r, horizontalColor.g, horizontalColor.b, horizontalColor.a }, true);
|
||||||
|
} finally {
|
||||||
|
blenderContext.getBlenderKey().setLoadGeneratedTextures(loadGeneratedTextures);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureCubeMap texture = null;
|
||||||
|
if (loadedTextures != null && loadedTextures.size() > 0) {
|
||||||
|
if (loadedTextures.size() > 1) {
|
||||||
|
throw new IllegalStateException("There should be only one combined texture for sky!");
|
||||||
|
}
|
||||||
|
CombinedTexture combinedTexture = loadedTextures.get(1);
|
||||||
|
texture = combinedTexture.generateSkyTexture(horizontalColor, zenithColor, blenderContext);
|
||||||
|
} else {
|
||||||
|
LOGGER.fine("Preparing colors for colorband.");
|
||||||
|
int colorbandType = ColorBand.IPO_CARDINAL;
|
||||||
|
List<ColorRGBA> colorbandColors = new ArrayList<ColorRGBA>(3);
|
||||||
|
colorbandColors.add(horizontalColor);
|
||||||
|
if ((skytype & SKYTYPE_BLEND) != 0) {
|
||||||
|
if ((skytype & SKYTYPE_PAPER) != 0) {
|
||||||
|
colorbandType = ColorBand.IPO_LINEAR;
|
||||||
|
}
|
||||||
|
if ((skytype & SKYTYPE_REAL) != 0) {
|
||||||
|
colorbandColors.add(0, zenithColor);
|
||||||
|
}
|
||||||
|
colorbandColors.add(zenithColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize();
|
||||||
|
|
||||||
|
List<Integer> positions = new ArrayList<Integer>(colorbandColors.size());
|
||||||
|
positions.add(0);
|
||||||
|
if (colorbandColors.size() == 2) {
|
||||||
|
positions.add(size - 1);
|
||||||
|
} else if (colorbandColors.size() == 3) {
|
||||||
|
positions.add(size / 2);
|
||||||
|
positions.add(size - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.fine("Generating sky texture.");
|
||||||
|
float[][] values = new ColorBand(colorbandType, colorbandColors, positions, size).computeValues();
|
||||||
|
|
||||||
|
Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6);
|
||||||
|
PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat());
|
||||||
|
TexturePixel pixel = new TexturePixel();
|
||||||
|
|
||||||
|
LOGGER.fine("Creating side textures.");
|
||||||
|
int[] sideImagesIndexes = new int[] { 0, 1, 4, 5 };
|
||||||
|
for (int i : sideImagesIndexes) {
|
||||||
|
for (int y = 0; y < size; ++y) {
|
||||||
|
pixel.red = values[y][0];
|
||||||
|
pixel.green = values[y][1];
|
||||||
|
pixel.blue = values[y][2];
|
||||||
|
|
||||||
|
for (int x = 0; x < size; ++x) {
|
||||||
|
pixelIO.write(image, i, pixel, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.fine("Creating top texture.");
|
||||||
|
pixelIO.read(image, 0, pixel, 0, image.getHeight() - 1);
|
||||||
|
for (int y = 0; y < size; ++y) {
|
||||||
|
for (int x = 0; x < size; ++x) {
|
||||||
|
pixelIO.write(image, 3, pixel, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
texture = new TextureCubeMap(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.fine("Sky texture created. Creating sky.");
|
||||||
|
return SkyFactory.createSky(blenderContext.getAssetManager(), texture, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
package com.jme3.scene.plugins.blender.materials;
|
package com.jme3.scene.plugins.blender.materials;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -21,15 +19,11 @@ import com.jme3.scene.VertexBuffer.Format;
|
|||||||
import com.jme3.scene.VertexBuffer.Usage;
|
import com.jme3.scene.VertexBuffer.Usage;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||||
import com.jme3.scene.plugins.blender.file.DynamicArray;
|
|
||||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
|
||||||
import com.jme3.scene.plugins.blender.file.Structure;
|
import com.jme3.scene.plugins.blender.file.Structure;
|
||||||
import com.jme3.scene.plugins.blender.materials.MaterialHelper.DiffuseShader;
|
import com.jme3.scene.plugins.blender.materials.MaterialHelper.DiffuseShader;
|
||||||
import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader;
|
import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader;
|
||||||
import com.jme3.scene.plugins.blender.textures.CombinedTexture;
|
import com.jme3.scene.plugins.blender.textures.CombinedTexture;
|
||||||
import com.jme3.scene.plugins.blender.textures.TextureHelper;
|
import com.jme3.scene.plugins.blender.textures.TextureHelper;
|
||||||
import com.jme3.scene.plugins.blender.textures.blending.TextureBlender;
|
|
||||||
import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory;
|
|
||||||
import com.jme3.texture.Texture;
|
import com.jme3.texture.Texture;
|
||||||
import com.jme3.util.BufferUtils;
|
import com.jme3.util.BufferUtils;
|
||||||
|
|
||||||
@ -63,7 +57,6 @@ public final class MaterialContext {
|
|||||||
/* package */final boolean vTangent;
|
/* package */final boolean vTangent;
|
||||||
/* package */FaceCullMode faceCullMode;
|
/* package */FaceCullMode faceCullMode;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
/* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
|
/* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException {
|
||||||
name = structure.getName();
|
name = structure.getName();
|
||||||
|
|
||||||
@ -101,56 +94,8 @@ public final class MaterialContext {
|
|||||||
ambientColor = new ColorRGBA(r, g, b, alpha);
|
ambientColor = new ColorRGBA(r, g, b, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");
|
|
||||||
int separatedTextures = ((Number) structure.getFieldValue("septex")).intValue();
|
|
||||||
List<TextureData> texturesList = new ArrayList<TextureData>();
|
|
||||||
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();
|
|
||||||
textureData.uvCoordinatesName = textureData.mtex.getFieldValue("uvName").toString();
|
|
||||||
if(textureData.uvCoordinatesName != null && textureData.uvCoordinatesName.trim().length() == 0) {
|
|
||||||
textureData.uvCoordinatesName = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Number, List<TextureData>> textureDataMap = this.sortAndFilterTextures(texturesList);
|
|
||||||
loadedTextures = new HashMap<Number, CombinedTexture>();
|
|
||||||
float[] diffuseColorArray = new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a };
|
|
||||||
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
|
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
|
||||||
for (Entry<Number, List<TextureData>> entry : textureDataMap.entrySet()) {
|
loadedTextures = textureHelper.readTextureData(structure, new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a }, false);
|
||||||
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, textureData.uvCoordinatesName, blenderContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (combinedTexture.getTexturesCount() > 0) {
|
|
||||||
loadedTextures.put(entry.getKey(), combinedTexture);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// veryfying if the transparency is present
|
// veryfying if the transparency is present
|
||||||
// (in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when
|
// (in blender transparent mask is 0x10000 but its better to verify it because blender can indicate transparency when
|
||||||
@ -301,34 +246,6 @@ public final class MaterialContext {
|
|||||||
return false;
|
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<Number, List<TextureData>> sortAndFilterTextures(List<TextureData> textures) {
|
|
||||||
int[] mappings = new int[] { MTEX_COL, MTEX_NOR, MTEX_EMIT, MTEX_SPEC, MTEX_ALPHA, MTEX_AMB };
|
|
||||||
Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>();
|
|
||||||
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<TextureData> datas = result.get(mappings[i]);
|
|
||||||
if (datas == null) {
|
|
||||||
datas = new ArrayList<TextureData>();
|
|
||||||
result.put(mappings[i], datas);
|
|
||||||
}
|
|
||||||
datas.add(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method sets the face cull mode.
|
* This method sets the face cull mode.
|
||||||
* @param faceCullMode
|
* @param faceCullMode
|
||||||
@ -393,13 +310,4 @@ public final class MaterialContext {
|
|||||||
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
|
float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue();
|
||||||
return new ColorRGBA(r, g, b, alpha);
|
return new ColorRGBA(r, g, b, alpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TextureData {
|
|
||||||
Structure mtex;
|
|
||||||
Structure textureStructure;
|
|
||||||
int uvCoordinatesType;
|
|
||||||
int projectionType;
|
|
||||||
/** The name of the user's UV coordinates that are used for this texture. */
|
|
||||||
String uvCoordinatesName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.jme3.scene.plugins.blender.textures;
|
package com.jme3.scene.plugins.blender.textures;
|
||||||
|
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
import com.jme3.math.FastMath;
|
import com.jme3.math.FastMath;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||||
@ -38,6 +39,7 @@ import com.jme3.scene.plugins.blender.file.DynamicArray;
|
|||||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||||
import com.jme3.scene.plugins.blender.file.Structure;
|
import com.jme3.scene.plugins.blender.file.Structure;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@ -59,8 +61,46 @@ public class ColorBand {
|
|||||||
public static final int IPO_CONSTANT = 4;
|
public static final int IPO_CONSTANT = 4;
|
||||||
|
|
||||||
private int cursorsAmount, ipoType;
|
private int cursorsAmount, ipoType;
|
||||||
|
/** The default amount of possible cursor positions. */
|
||||||
|
private int resultSize = 1001;
|
||||||
private ColorBandData[] data;
|
private ColorBandData[] data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A constructor used to instantiate color band by hand instead of reading it from the blend file.
|
||||||
|
* @param ipoType
|
||||||
|
* the interpolation type
|
||||||
|
* @param colors
|
||||||
|
* the colorband colors
|
||||||
|
* @param positions
|
||||||
|
* the positions for colors' cursors
|
||||||
|
* @param resultSize
|
||||||
|
* the size of the result table
|
||||||
|
*/
|
||||||
|
public ColorBand(int ipoType, List<ColorRGBA> colors, List<Integer> positions, int resultSize) {
|
||||||
|
if (colors == null || colors.size() < 1) {
|
||||||
|
throw new IllegalArgumentException("The amount of colorband's colors must be at least 1.");
|
||||||
|
}
|
||||||
|
if (ipoType < IPO_LINEAR || ipoType > IPO_CONSTANT) {
|
||||||
|
throw new IllegalArgumentException("Unknown colorband interpolation type: " + ipoType);
|
||||||
|
}
|
||||||
|
if (positions == null || positions.size() != colors.size()) {
|
||||||
|
throw new IllegalArgumentException("The size of positions and colors list should be equal!");
|
||||||
|
}
|
||||||
|
for (Integer position : positions) {
|
||||||
|
if (position.intValue() < 0 || position.intValue() >= resultSize) {
|
||||||
|
throw new IllegalArgumentException("Invalid position value: " + position + "! Should be from range: [0, " + resultSize + "]!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursorsAmount = colors.size();
|
||||||
|
this.ipoType = ipoType;
|
||||||
|
this.resultSize = resultSize;
|
||||||
|
data = new ColorBandData[this.cursorsAmount];
|
||||||
|
for (int i = 0; i < cursorsAmount; ++i) {
|
||||||
|
data[i] = new ColorBandData(colors.get(i), positions.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Loads the data from the given structure.
|
* Constructor. Loads the data from the given structure.
|
||||||
* @param tex
|
* @param tex
|
||||||
@ -113,11 +153,8 @@ public class ColorBand {
|
|||||||
public float[][] computeValues() {
|
public float[][] computeValues() {
|
||||||
float[][] result = null;
|
float[][] result = null;
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
result = new float[1001][4];// 1001 - amount of possible cursor
|
result = new float[resultSize][4];// resultSize - amount of possible cursor positions; 4 = [r, g, b, a]
|
||||||
// positions; 4 = [r, g, b, a]
|
if (data.length == 1) {// special case; use only one color for all types of colorband interpolation
|
||||||
|
|
||||||
if (data.length == 1) {// special case; use only one color for all
|
|
||||||
// types of colorband interpolation
|
|
||||||
for (int i = 0; i < result.length; ++i) {
|
for (int i = 0; i < result.length; ++i) {
|
||||||
result[i][0] = data[0].r;
|
result[i][0] = data[0].r;
|
||||||
result[i][1] = data[0].g;
|
result[i][1] = data[0].g;
|
||||||
@ -167,7 +204,7 @@ public class ColorBand {
|
|||||||
if (data[0].pos == 0) {
|
if (data[0].pos == 0) {
|
||||||
cbDataMap.put(Integer.valueOf(-1), data[0]);
|
cbDataMap.put(Integer.valueOf(-1), data[0]);
|
||||||
} else {
|
} else {
|
||||||
ColorBandData cbData = data[0].clone();
|
ColorBandData cbData = new ColorBandData(data[0]);
|
||||||
cbData.pos = 0;
|
cbData.pos = 0;
|
||||||
cbDataMap.put(Integer.valueOf(-1), cbData);
|
cbDataMap.put(Integer.valueOf(-1), cbData);
|
||||||
cbDataMap.put(Integer.valueOf(-2), cbData);
|
cbDataMap.put(Integer.valueOf(-2), cbData);
|
||||||
@ -176,7 +213,7 @@ public class ColorBand {
|
|||||||
if (data[data.length - 1].pos == 1000) {
|
if (data[data.length - 1].pos == 1000) {
|
||||||
cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]);
|
cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]);
|
||||||
} else {
|
} else {
|
||||||
ColorBandData cbData = data[data.length - 1].clone();
|
ColorBandData cbData = new ColorBandData(data[data.length - 1]);
|
||||||
cbData.pos = 1000;
|
cbData.pos = 1000;
|
||||||
cbDataMap.put(Integer.valueOf(data.length), cbData);
|
cbDataMap.put(Integer.valueOf(data.length), cbData);
|
||||||
cbDataMap.put(Integer.valueOf(data.length + 1), cbData);
|
cbDataMap.put(Integer.valueOf(data.length + 1), cbData);
|
||||||
@ -307,7 +344,7 @@ public class ColorBand {
|
|||||||
*
|
*
|
||||||
* @author Marcin Roguski (Kaelthas)
|
* @author Marcin Roguski (Kaelthas)
|
||||||
*/
|
*/
|
||||||
private static class ColorBandData implements Cloneable {
|
private static class ColorBandData {
|
||||||
public final float r, g, b, a;
|
public final float r, g, b, a;
|
||||||
public int pos;
|
public int pos;
|
||||||
|
|
||||||
@ -316,6 +353,21 @@ public class ColorBand {
|
|||||||
a = 1;
|
a = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor that stores the color and position of the cursor.
|
||||||
|
* @param color
|
||||||
|
* the cursor's color
|
||||||
|
* @param pos
|
||||||
|
* the cursor's position
|
||||||
|
*/
|
||||||
|
public ColorBandData(ColorRGBA color, int pos) {
|
||||||
|
r = color.r;
|
||||||
|
g = color.g;
|
||||||
|
b = color.b;
|
||||||
|
a = color.a;
|
||||||
|
this.pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy constructor.
|
* Copy constructor.
|
||||||
*/
|
*/
|
||||||
@ -341,15 +393,6 @@ public class ColorBand {
|
|||||||
this.pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f);
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "P: " + this.pos + " [" + this.r + ", " + this.g + ", " + this.b + ", " + this.a + "]";
|
return "P: " + this.pos + " [" + this.r + ", " + this.g + ", " + this.b + ", " + this.a + "]";
|
||||||
|
@ -3,13 +3,16 @@ package com.jme3.scene.plugins.blender.textures;
|
|||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import jme3tools.converters.ImageToAwt;
|
import jme3tools.converters.ImageToAwt;
|
||||||
|
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
import com.jme3.math.Vector2f;
|
import com.jme3.math.Vector2f;
|
||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.Mesh;
|
import com.jme3.scene.Mesh;
|
||||||
@ -30,6 +33,8 @@ import com.jme3.texture.Texture.MagFilter;
|
|||||||
import com.jme3.texture.Texture.MinFilter;
|
import com.jme3.texture.Texture.MinFilter;
|
||||||
import com.jme3.texture.Texture.WrapMode;
|
import com.jme3.texture.Texture.WrapMode;
|
||||||
import com.jme3.texture.Texture2D;
|
import com.jme3.texture.Texture2D;
|
||||||
|
import com.jme3.texture.TextureCubeMap;
|
||||||
|
import com.jme3.util.BufferUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a texture that is defined for the material. It can be
|
* This class represents a texture that is defined for the material. It can be
|
||||||
@ -43,6 +48,11 @@ public class CombinedTexture {
|
|||||||
|
|
||||||
/** The mapping type of the texture. Defined bu MaterialContext.MTEX_COL, MTEX_NOR etc. */
|
/** The mapping type of the texture. Defined bu MaterialContext.MTEX_COL, MTEX_NOR etc. */
|
||||||
private final int mappingType;
|
private final int mappingType;
|
||||||
|
/**
|
||||||
|
* If set to true then if a texture without alpha is added then all textures below are discarded because
|
||||||
|
* the new one will cover them anyway. If set to false then all textures are stored.
|
||||||
|
*/
|
||||||
|
private boolean discardCoveredTextures;
|
||||||
/** The data for each of the textures. */
|
/** The data for each of the textures. */
|
||||||
private List<TextureData> textureDatas = new ArrayList<TextureData>();
|
private List<TextureData> textureDatas = new ArrayList<TextureData>();
|
||||||
/** The result texture. */
|
/** The result texture. */
|
||||||
@ -55,9 +65,13 @@ public class CombinedTexture {
|
|||||||
*
|
*
|
||||||
* @param mappingType
|
* @param mappingType
|
||||||
* texture mapping type
|
* texture mapping type
|
||||||
|
* @param discardCoveredTextures
|
||||||
|
* if set to true then if a texture without alpha is added then all textures below are discarded because
|
||||||
|
* the new one will cover them anyway, if set to false then all textures are stored
|
||||||
*/
|
*/
|
||||||
public CombinedTexture(int mappingType) {
|
public CombinedTexture(int mappingType, boolean discardCoveredTextures) {
|
||||||
this.mappingType = mappingType;
|
this.mappingType = mappingType;
|
||||||
|
this.discardCoveredTextures = discardCoveredTextures;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,7 +89,7 @@ public class CombinedTexture {
|
|||||||
* @param textureStructure
|
* @param textureStructure
|
||||||
* the texture sructure
|
* the texture sructure
|
||||||
* @param uvCoordinatesName
|
* @param uvCoordinatesName
|
||||||
* the name of the used user's UV coordinates for this texture
|
* the name of the used user's UV coordinates for this texture
|
||||||
* @param blenderContext
|
* @param blenderContext
|
||||||
* the blender context
|
* the blender context
|
||||||
*/
|
*/
|
||||||
@ -93,7 +107,7 @@ public class CombinedTexture {
|
|||||||
textureData.textureStructure = textureStructure;
|
textureData.textureStructure = textureStructure;
|
||||||
textureData.uvCoordinatesName = uvCoordinatesName;
|
textureData.uvCoordinatesName = uvCoordinatesName;
|
||||||
|
|
||||||
if (textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) {
|
if (discardCoveredTextures && textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) {
|
||||||
textureDatas.clear();// clear previous textures, they will be covered anyway
|
textureDatas.clear();// clear previous textures, they will be covered anyway
|
||||||
}
|
}
|
||||||
textureDatas.add(textureData);
|
textureDatas.add(textureData);
|
||||||
@ -120,7 +134,6 @@ public class CombinedTexture {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void flatten(Geometry geometry, Long geometriesOMA, LinkedHashMap<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
|
public void flatten(Geometry geometry, Long geometriesOMA, LinkedHashMap<String, List<Vector2f>> userDefinedUVCoordinates, BlenderContext blenderContext) {
|
||||||
TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class);
|
|
||||||
Mesh mesh = geometry.getMesh();
|
Mesh mesh = geometry.getMesh();
|
||||||
Texture previousTexture = null;
|
Texture previousTexture = null;
|
||||||
UVCoordinatesType masterUVCoordinatesType = null;
|
UVCoordinatesType masterUVCoordinatesType = null;
|
||||||
@ -128,7 +141,7 @@ public class CombinedTexture {
|
|||||||
for (TextureData textureData : textureDatas) {
|
for (TextureData textureData : textureDatas) {
|
||||||
// decompress compressed textures (all will be merged into one texture anyway)
|
// decompress compressed textures (all will be merged into one texture anyway)
|
||||||
if (textureDatas.size() > 1 && textureData.texture.getImage().getFormat().isCompressed()) {
|
if (textureDatas.size() > 1 && textureData.texture.getImage().getFormat().isCompressed()) {
|
||||||
textureData.texture.setImage(textureHelper.decompress(textureData.texture.getImage()));
|
textureData.texture.setImage(ImageUtils.decompress(textureData.texture.getImage()));
|
||||||
textureData.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender);
|
textureData.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,8 +152,8 @@ public class CombinedTexture {
|
|||||||
resultTexture = textureData.texture;
|
resultTexture = textureData.texture;
|
||||||
|
|
||||||
if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
|
if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
|
||||||
if(textureData.uvCoordinatesName == null) {
|
if (textureData.uvCoordinatesName == null) {
|
||||||
resultUVS = userDefinedUVCoordinates.values().iterator().next();//get the first UV available
|
resultUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available
|
||||||
} else {
|
} else {
|
||||||
resultUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName);
|
resultUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName);
|
||||||
}
|
}
|
||||||
@ -167,11 +180,9 @@ public class CombinedTexture {
|
|||||||
triangulatedTexture.blend(textureData.textureBlender, (TriangulatedTexture) resultTexture, blenderContext);
|
triangulatedTexture.blend(textureData.textureBlender, (TriangulatedTexture) resultTexture, blenderContext);
|
||||||
resultTexture = previousTexture = triangulatedTexture;
|
resultTexture = previousTexture = triangulatedTexture;
|
||||||
} else if (textureData.texture instanceof Texture2D) {
|
} else if (textureData.texture instanceof Texture2D) {
|
||||||
if (this.isUVTypesMatch(masterUVCoordinatesType, masterUserUVSetName,
|
if (this.isUVTypesMatch(masterUVCoordinatesType, masterUserUVSetName, textureData.uvCoordinatesType, textureData.uvCoordinatesName) && resultTexture instanceof Texture2D) {
|
||||||
textureData.uvCoordinatesType, textureData.uvCoordinatesName) &&
|
|
||||||
resultTexture instanceof Texture2D) {
|
|
||||||
this.scale((Texture2D) textureData.texture, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight());
|
this.scale((Texture2D) textureData.texture, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight());
|
||||||
this.merge((Texture2D) resultTexture, (Texture2D) textureData.texture);
|
ImageUtils.merge(resultTexture.getImage(), textureData.texture.getImage());
|
||||||
previousTexture = resultTexture;
|
previousTexture = resultTexture;
|
||||||
} else {
|
} else {
|
||||||
if (!(resultTexture instanceof TriangulatedTexture)) {
|
if (!(resultTexture instanceof TriangulatedTexture)) {
|
||||||
@ -181,8 +192,8 @@ public class CombinedTexture {
|
|||||||
// first triangulate the current texture
|
// first triangulate the current texture
|
||||||
List<Vector2f> textureUVS = null;
|
List<Vector2f> textureUVS = null;
|
||||||
if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
|
if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) {
|
||||||
if(textureData.uvCoordinatesName == null) {
|
if (textureData.uvCoordinatesName == null) {
|
||||||
textureUVS = userDefinedUVCoordinates.values().iterator().next();//get the first UV available
|
textureUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available
|
||||||
} else {
|
} else {
|
||||||
textureUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName);
|
textureUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName);
|
||||||
}
|
}
|
||||||
@ -193,7 +204,10 @@ public class CombinedTexture {
|
|||||||
TriangulatedTexture triangulatedTexture = new TriangulatedTexture((Texture2D) textureData.texture, textureUVS, blenderContext);
|
TriangulatedTexture triangulatedTexture = new TriangulatedTexture((Texture2D) textureData.texture, textureUVS, blenderContext);
|
||||||
// then move the texture to different UV's
|
// then move the texture to different UV's
|
||||||
triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext);
|
triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext);
|
||||||
((TriangulatedTexture) resultTexture).merge(triangulatedTexture);
|
// merge triangulated textures
|
||||||
|
for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) {
|
||||||
|
ImageUtils.merge(((TriangulatedTexture) resultTexture).getFaceTextureElement(i).image, triangulatedTexture.getImage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,7 +217,7 @@ public class CombinedTexture {
|
|||||||
if (mappingType == MaterialContext.MTEX_NOR) {
|
if (mappingType == MaterialContext.MTEX_NOR) {
|
||||||
for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) {
|
for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) {
|
||||||
TriangleTextureElement triangleTextureElement = ((TriangulatedTexture) resultTexture).getFaceTextureElement(i);
|
TriangleTextureElement triangleTextureElement = ((TriangulatedTexture) resultTexture).getFaceTextureElement(i);
|
||||||
triangleTextureElement.image = textureHelper.convertToNormalMapTexture(triangleTextureElement.image, 1);// TODO: get proper strength factor
|
triangleTextureElement.image = ImageUtils.convertToNormalMapTexture(triangleTextureElement.image, 1);// TODO: get proper strength factor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS();
|
resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS();
|
||||||
@ -218,22 +232,102 @@ public class CombinedTexture {
|
|||||||
resultTexture.setMinFilter(MinFilter.NearestNoMipMaps);
|
resultTexture.setMinFilter(MinFilter.NearestNoMipMaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a texture that will be used by the sky spatial.
|
||||||
|
* The result texture has 6 layers. Every image in each layer has equal size and its shape is a square.
|
||||||
|
* The size of each image is the maximum size (width or height) of the textures given.
|
||||||
|
* The default sky generated texture size is used (this value is set in the BlenderKey) if no picture textures
|
||||||
|
* are present or their sizes is lower than the generated texture size.
|
||||||
|
* The textures of lower sizes are properly scaled.
|
||||||
|
* All the textures are mixed into one and put as layers in the result texture.
|
||||||
|
*
|
||||||
|
* @param horizontalColor
|
||||||
|
* the horizon color
|
||||||
|
* @param zenithColor
|
||||||
|
* the zenith color
|
||||||
|
* @param blenderContext
|
||||||
|
* the blender context
|
||||||
|
* @return texture for the sky
|
||||||
|
*/
|
||||||
|
public TextureCubeMap generateSkyTexture(ColorRGBA horizontalColor, ColorRGBA zenithColor, BlenderContext blenderContext) {
|
||||||
|
LOGGER.log(Level.FINE, "Preparing sky texture from {0} applied textures.", textureDatas.size());
|
||||||
|
|
||||||
|
LOGGER.fine("Computing the texture size.");
|
||||||
|
int size = -1;
|
||||||
|
for (TextureData textureData : textureDatas) {
|
||||||
|
if (textureData.texture instanceof Texture2D) {
|
||||||
|
size = Math.max(textureData.texture.getImage().getWidth(), size);
|
||||||
|
size = Math.max(textureData.texture.getImage().getHeight(), size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (size < 0) {
|
||||||
|
size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize();
|
||||||
|
}
|
||||||
|
LOGGER.log(Level.FINE, "The sky texture size will be: {0}x{0}.", size);
|
||||||
|
|
||||||
|
TextureCubeMap result = null;
|
||||||
|
for (TextureData textureData : textureDatas) {
|
||||||
|
TextureCubeMap texture = null;
|
||||||
|
if (textureData.texture instanceof GeneratedTexture) {
|
||||||
|
texture = ((GeneratedTexture) textureData.texture).generateSkyTexture(size, horizontalColor, zenithColor);
|
||||||
|
} else {
|
||||||
|
// first create a grayscale version of the image
|
||||||
|
Image image = textureData.texture.getImage();
|
||||||
|
if (image.getWidth() != image.getHeight() || image.getWidth() != size) {
|
||||||
|
image = ImageUtils.resizeTo(image, size, size);
|
||||||
|
}
|
||||||
|
Image grayscaleImage = ImageUtils.convertToGrayscaleTexture(image);
|
||||||
|
|
||||||
|
// add the sky colors to the image
|
||||||
|
PixelInputOutput sourcePixelIO = PixelIOFactory.getPixelIO(grayscaleImage.getFormat());
|
||||||
|
PixelInputOutput targetPixelIO = PixelIOFactory.getPixelIO(image.getFormat());
|
||||||
|
TexturePixel texturePixel = new TexturePixel();
|
||||||
|
for (int x = 0; x < image.getWidth(); ++x) {
|
||||||
|
for (int y = 0; y < image.getHeight(); ++y) {
|
||||||
|
sourcePixelIO.read(grayscaleImage, 0, texturePixel, x, y);
|
||||||
|
texturePixel.intensity = texturePixel.red;// no matter which factor we use here, in grayscale they are all equal
|
||||||
|
ImageUtils.color(texturePixel, horizontalColor, zenithColor);
|
||||||
|
targetPixelIO.write(image, 0, texturePixel, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the cubemap texture from the coloured image
|
||||||
|
ByteBuffer sourceData = image.getData(0);
|
||||||
|
ArrayList<ByteBuffer> data = new ArrayList<ByteBuffer>(6);
|
||||||
|
for (int i = 0; i < 6; ++i) {
|
||||||
|
data.add(BufferUtils.clone(sourceData));
|
||||||
|
}
|
||||||
|
texture = new TextureCubeMap(new Image(image.getFormat(), image.getWidth(), image.getHeight(), 6, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
result = texture;
|
||||||
|
} else {
|
||||||
|
ImageUtils.mix(result.getImage(), texture.getImage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The method checks if the texture UV coordinates match.
|
* The method checks if the texture UV coordinates match.
|
||||||
* It the types are equal and different then UVCoordinatesType.TEXCO_UV then we consider them a match.
|
* It the types are equal and different then UVCoordinatesType.TEXCO_UV then we consider them a match.
|
||||||
* If they are both UVCoordinatesType.TEXCO_UV then they match only when their UV sets names are equal.
|
* If they are both UVCoordinatesType.TEXCO_UV then they match only when their UV sets names are equal.
|
||||||
* In other cases they are considered NOT a match.
|
* In other cases they are considered NOT a match.
|
||||||
* @param type1 the UV coord type
|
* @param type1
|
||||||
* @param uvSetName1 the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
|
* the UV coord type
|
||||||
* @param type2 the UV coord type
|
* @param uvSetName1
|
||||||
* @param uvSetName2 the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
|
* the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
|
||||||
|
* @param type2
|
||||||
|
* the UV coord type
|
||||||
|
* @param uvSetName2
|
||||||
|
* the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV)
|
||||||
* @return <b>true</b> if the types match and <b>false</b> otherwise
|
* @return <b>true</b> if the types match and <b>false</b> otherwise
|
||||||
*/
|
*/
|
||||||
private boolean isUVTypesMatch(UVCoordinatesType type1, String uvSetName1,
|
private boolean isUVTypesMatch(UVCoordinatesType type1, String uvSetName1, UVCoordinatesType type2, String uvSetName2) {
|
||||||
UVCoordinatesType type2, String uvSetName2) {
|
if (type1 == type2) {
|
||||||
if(type1 == type2) {
|
if (type1 == UVCoordinatesType.TEXCO_UV) {
|
||||||
if(type1 == UVCoordinatesType.TEXCO_UV) {
|
if (uvSetName1 != null && uvSetName2 != null && uvSetName1.equals(uvSetName2)) {
|
||||||
if(uvSetName1 != null && uvSetName2 != null && uvSetName1.equals(uvSetName2)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -299,39 +393,6 @@ public class CombinedTexture {
|
|||||||
return false;
|
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.
|
* This method determines if the given texture has no alpha channel.
|
||||||
*
|
*
|
||||||
@ -460,6 +521,6 @@ public class CombinedTexture {
|
|||||||
/** The texture sructure. */
|
/** The texture sructure. */
|
||||||
public Structure textureStructure;
|
public Structure textureStructure;
|
||||||
/** The name of the user's UV coordinates that are used for this texture. */
|
/** The name of the user's UV coordinates that are used for this texture. */
|
||||||
public String uvCoordinatesName;
|
public String uvCoordinatesName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
package com.jme3.scene.plugins.blender.textures;
|
package com.jme3.scene.plugins.blender.textures;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import com.jme3.bounding.BoundingBox;
|
import com.jme3.bounding.BoundingBox;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.FastMath;
|
||||||
import com.jme3.math.Vector3f;
|
import com.jme3.math.Vector3f;
|
||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.Mesh;
|
import com.jme3.scene.Mesh;
|
||||||
@ -10,12 +17,12 @@ import com.jme3.scene.plugins.blender.file.Structure;
|
|||||||
import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement;
|
import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement;
|
||||||
import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType;
|
import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType;
|
||||||
import com.jme3.scene.plugins.blender.textures.generating.TextureGenerator;
|
import com.jme3.scene.plugins.blender.textures.generating.TextureGenerator;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
|
||||||
import com.jme3.texture.Image;
|
import com.jme3.texture.Image;
|
||||||
|
import com.jme3.texture.Image.Format;
|
||||||
import com.jme3.texture.Texture;
|
import com.jme3.texture.Texture;
|
||||||
import java.util.Comparator;
|
import com.jme3.texture.TextureCubeMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The generated texture loaded from blender file. The texture is not generated
|
* The generated texture loaded from blender file. The texture is not generated
|
||||||
@ -25,6 +32,13 @@ import java.util.TreeSet;
|
|||||||
* @author Marcin Roguski (Kaelthas)
|
* @author Marcin Roguski (Kaelthas)
|
||||||
*/
|
*/
|
||||||
/* package */class GeneratedTexture extends Texture {
|
/* package */class GeneratedTexture extends Texture {
|
||||||
|
private static final int POSITIVE_X = 0;
|
||||||
|
private static final int NEGATIVE_X = 1;
|
||||||
|
// private static final int POSITIVE_Y = 2;
|
||||||
|
private static final int NEGATIVE_Y = 3;
|
||||||
|
private static final int POSITIVE_Z = 4;
|
||||||
|
private static final int NEGATIVE_Z = 5;
|
||||||
|
|
||||||
// flag values
|
// flag values
|
||||||
public static final int TEX_COLORBAND = 1;
|
public static final int TEX_COLORBAND = 1;
|
||||||
public static final int TEX_FLIPBLEND = 2;
|
public static final int TEX_FLIPBLEND = 2;
|
||||||
@ -115,6 +129,61 @@ import java.util.TreeSet;
|
|||||||
return new TriangulatedTexture(triangleTextureElements, blenderContext);
|
return new TriangulatedTexture(triangleTextureElements, blenderContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a texture for the sky. The result texture has 6 layers.
|
||||||
|
* @param size
|
||||||
|
* the size of the texture (width and height are equal)
|
||||||
|
* @param horizontalColor
|
||||||
|
* the horizon color
|
||||||
|
* @param zenithColor
|
||||||
|
* the zenith color
|
||||||
|
* @return the sky texture
|
||||||
|
*/
|
||||||
|
public TextureCubeMap generateSkyTexture(int size, ColorRGBA horizontalColor, ColorRGBA zenithColor) {
|
||||||
|
Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6);
|
||||||
|
PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat());
|
||||||
|
TexturePixel pixel = new TexturePixel();
|
||||||
|
|
||||||
|
float delta = 1 / (float) (size - 1);
|
||||||
|
float sideV, sideS = 1, forwardU = 1, forwardV, upS;
|
||||||
|
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
for (int x = 0; x < size; ++x) {
|
||||||
|
sideV = 1;
|
||||||
|
forwardV = 1;
|
||||||
|
upS = 0;
|
||||||
|
for (int y = 0; y < size; ++y) {
|
||||||
|
textureGenerator.getPixel(pixel, 1, sideV, sideS);
|
||||||
|
pixelIO.write(image, NEGATIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// right
|
||||||
|
|
||||||
|
textureGenerator.getPixel(pixel, 0, sideV, 1 - sideS);
|
||||||
|
pixelIO.write(image, POSITIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// left
|
||||||
|
|
||||||
|
textureGenerator.getPixel(pixel, forwardU, forwardV, 0);
|
||||||
|
pixelIO.write(image, POSITIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// front
|
||||||
|
|
||||||
|
textureGenerator.getPixel(pixel, 1 - forwardU, forwardV, 1);
|
||||||
|
pixelIO.write(image, NEGATIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// back
|
||||||
|
|
||||||
|
textureGenerator.getPixel(pixel, forwardU, 0, upS);
|
||||||
|
pixelIO.write(image, NEGATIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// top
|
||||||
|
|
||||||
|
// textureGenerator.getPixel(pixel, forwardU, 1, upS);
|
||||||
|
// pixelIO.write(image, POSITIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);//bottom
|
||||||
|
|
||||||
|
sideV = FastMath.clamp(sideV - delta, 0, 1);
|
||||||
|
forwardV = FastMath.clamp(forwardV - delta, 0, 1);
|
||||||
|
upS = FastMath.clamp(upS + delta, 0, 1);
|
||||||
|
}
|
||||||
|
sideS = FastMath.clamp(sideS - delta, 0, 1);
|
||||||
|
forwardU = FastMath.clamp(forwardU - delta, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println(System.currentTimeMillis() - time);
|
||||||
|
|
||||||
|
return new TextureCubeMap(image);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setWrap(WrapAxis axis, WrapMode mode) {
|
public void setWrap(WrapAxis axis, WrapMode mode) {
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,471 @@
|
|||||||
|
package com.jme3.scene.plugins.blender.textures;
|
||||||
|
|
||||||
|
import java.awt.color.ColorSpace;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.AffineTransformOp;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.ColorConvertOp;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import jme3tools.converters.ImageToAwt;
|
||||||
|
import jme3tools.converters.RGB565;
|
||||||
|
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
|
||||||
|
import com.jme3.texture.Image;
|
||||||
|
import com.jme3.texture.Image.Format;
|
||||||
|
import com.jme3.util.BufferUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This utility class has the methods that deal with images.
|
||||||
|
*
|
||||||
|
* @author Marcin Roguski (Kaelthas)
|
||||||
|
*/
|
||||||
|
public final class ImageUtils {
|
||||||
|
/**
|
||||||
|
* Creates an image of the given size and depth.
|
||||||
|
* @param format
|
||||||
|
* the image format
|
||||||
|
* @param width
|
||||||
|
* the image width
|
||||||
|
* @param height
|
||||||
|
* the image height
|
||||||
|
* @param depth
|
||||||
|
* the image depth
|
||||||
|
* @return the new image instance
|
||||||
|
*/
|
||||||
|
public static Image createEmptyImage(Format format, int width, int height, int depth) {
|
||||||
|
int bufferSize = width * height * (format.getBitsPerPixel() >> 3);
|
||||||
|
if (depth < 2) {
|
||||||
|
return new Image(format, width, height, BufferUtils.createByteBuffer(bufferSize));
|
||||||
|
}
|
||||||
|
ArrayList<ByteBuffer> data = new ArrayList<ByteBuffer>(depth);
|
||||||
|
for (int i = 0; i < depth; ++i) {
|
||||||
|
data.add(BufferUtils.createByteBuffer(bufferSize));
|
||||||
|
}
|
||||||
|
return new Image(Format.RGB8, width, height, depth, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method sets a color for the given pixel by merging the two given colors.
|
||||||
|
* The lowIntensityColor will be most visible when the pixel has low intensity.
|
||||||
|
* The highIntensityColor will be most visible when the pixel has high intensity.
|
||||||
|
*
|
||||||
|
* @param pixel
|
||||||
|
* the pixel that will have the colors altered
|
||||||
|
* @param lowIntensityColor
|
||||||
|
* the low intensity color
|
||||||
|
* @param highIntensityColor
|
||||||
|
* the high intensity color
|
||||||
|
* @return the altered pixel (the same instance)
|
||||||
|
*/
|
||||||
|
public static TexturePixel color(TexturePixel pixel, ColorRGBA lowIntensityColor, ColorRGBA highIntensityColor) {
|
||||||
|
float intensity = pixel.intensity;
|
||||||
|
pixel.fromColor(lowIntensityColor);
|
||||||
|
pixel.mult(1 - pixel.intensity);
|
||||||
|
pixel.add(highIntensityColor.mult(intensity));
|
||||||
|
return pixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method merges two given images. The result is stored in the
|
||||||
|
* 'target' image.
|
||||||
|
*
|
||||||
|
* @param targetImage
|
||||||
|
* the target image
|
||||||
|
* @param sourceImage
|
||||||
|
* the source image
|
||||||
|
*/
|
||||||
|
public static void merge(Image targetImage, Image sourceImage) {
|
||||||
|
if (sourceImage.getDepth() != targetImage.getDepth()) {
|
||||||
|
throw new IllegalArgumentException("The given images should have the same depth to merge them!");
|
||||||
|
}
|
||||||
|
if (sourceImage.getWidth() != targetImage.getWidth()) {
|
||||||
|
throw new IllegalArgumentException("The given images should have the same width to merge them!");
|
||||||
|
}
|
||||||
|
if (sourceImage.getHeight() != targetImage.getHeight()) {
|
||||||
|
throw new IllegalArgumentException("The given images should have the same height to merge them!");
|
||||||
|
}
|
||||||
|
|
||||||
|
PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
|
||||||
|
PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
|
||||||
|
TexturePixel sourcePixel = new TexturePixel();
|
||||||
|
TexturePixel targetPixel = new TexturePixel();
|
||||||
|
int depth = targetImage.getDepth() == 0 ? 1 : targetImage.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 merges two given images. The result is stored in the
|
||||||
|
* 'target' image.
|
||||||
|
*
|
||||||
|
* @param targetImage
|
||||||
|
* the target image
|
||||||
|
* @param sourceImage
|
||||||
|
* the source image
|
||||||
|
*/
|
||||||
|
public static void mix(Image targetImage, Image sourceImage) {
|
||||||
|
if (sourceImage.getDepth() != targetImage.getDepth()) {
|
||||||
|
throw new IllegalArgumentException("The given images should have the same depth to merge them!");
|
||||||
|
}
|
||||||
|
if (sourceImage.getWidth() != targetImage.getWidth()) {
|
||||||
|
throw new IllegalArgumentException("The given images should have the same width to merge them!");
|
||||||
|
}
|
||||||
|
if (sourceImage.getHeight() != targetImage.getHeight()) {
|
||||||
|
throw new IllegalArgumentException("The given images should have the same height to merge them!");
|
||||||
|
}
|
||||||
|
|
||||||
|
PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat());
|
||||||
|
PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat());
|
||||||
|
TexturePixel sourcePixel = new TexturePixel();
|
||||||
|
TexturePixel targetPixel = new TexturePixel();
|
||||||
|
int depth = targetImage.getDepth() == 0 ? 1 : targetImage.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.mix(sourcePixel);
|
||||||
|
targetIO.write(targetImage, layerIndex, targetPixel, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizes the image to the given width and height.
|
||||||
|
* @param source
|
||||||
|
* the source image (this remains untouched, the new image instance is created)
|
||||||
|
* @param width
|
||||||
|
* the target image width
|
||||||
|
* @param height
|
||||||
|
* the target image height
|
||||||
|
* @return the resized image
|
||||||
|
*/
|
||||||
|
public static Image resizeTo(Image source, int width, int height) {
|
||||||
|
BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0);
|
||||||
|
|
||||||
|
double scaleX = width / (double) sourceImage.getWidth();
|
||||||
|
double scaleY = height / (double) sourceImage.getHeight();
|
||||||
|
AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
|
||||||
|
AffineTransformOp bilinearScaleOp = new AffineTransformOp(scaleTransform, AffineTransformOp.TYPE_BILINEAR);
|
||||||
|
|
||||||
|
BufferedImage scaledImage = bilinearScaleOp.filter(sourceImage, new BufferedImage(width, height, sourceImage.getType()));
|
||||||
|
return ImageUtils.toJmeImage(scaledImage, source.getFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 static 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 * ImageUtils.getHeight(heightMap, x + 1, y) - strengthFactor * ImageUtils.getHeight(heightMap, x - 1, y);
|
||||||
|
T.x = 0;
|
||||||
|
T.y = 1;
|
||||||
|
T.z = strengthFactor * ImageUtils.getHeight(heightMap, x, y + 1) - strengthFactor * ImageUtils.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, ImageUtils.vectorToColor(N.x, N.y, N.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ImageUtils.toJmeImage(bumpMap, source.getFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method converts the given texture into black and whit (grayscale) texture.
|
||||||
|
*
|
||||||
|
* @param source
|
||||||
|
* the source texture
|
||||||
|
* @return grayscale texture
|
||||||
|
*/
|
||||||
|
public static Image convertToGrayscaleTexture(Image source) {
|
||||||
|
BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0);
|
||||||
|
ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);
|
||||||
|
op.filter(sourceImage, sourceImage);
|
||||||
|
return ImageUtils.toJmeImage(sourceImage, source.getFormat());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 static Image decompress(Image image) {
|
||||||
|
Format format = image.getFormat();
|
||||||
|
int depth = image.getDepth();
|
||||||
|
if (depth == 0) {
|
||||||
|
depth = 1;
|
||||||
|
}
|
||||||
|
ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(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<DDSTexelData> texelDataList = new ArrayList<DDSTexelData>(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
|
||||||
|
*/
|
||||||
|
private static 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
|
||||||
|
*/
|
||||||
|
private static 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts java awt image to jme image.
|
||||||
|
* @param bufferedImage
|
||||||
|
* the java awt image
|
||||||
|
* @param format
|
||||||
|
* the result image format
|
||||||
|
* @return the jme image
|
||||||
|
*/
|
||||||
|
private static Image toJmeImage(BufferedImage bufferedImage, Format format) {
|
||||||
|
ByteBuffer byteBuffer = BufferUtils.createByteBuffer(bufferedImage.getWidth() * bufferedImage.getHeight() * 3);
|
||||||
|
ImageToAwt.convert(bufferedImage, format, byteBuffer);
|
||||||
|
return new Image(format, bufferedImage.getWidth(), bufferedImage.getHeight(), byteBuffer);
|
||||||
|
}
|
||||||
|
}
|
@ -31,21 +31,17 @@
|
|||||||
*/
|
*/
|
||||||
package com.jme3.scene.plugins.blender.textures;
|
package com.jme3.scene.plugins.blender.textures;
|
||||||
|
|
||||||
import com.jme3.asset.AssetInfo;
|
|
||||||
|
|
||||||
import java.awt.color.ColorSpace;
|
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.awt.image.ColorConvertOp;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import jme3tools.converters.ImageToAwt;
|
import com.jme3.asset.AssetInfo;
|
||||||
import jme3tools.converters.RGB565;
|
|
||||||
|
|
||||||
import com.jme3.asset.AssetManager;
|
import com.jme3.asset.AssetManager;
|
||||||
import com.jme3.asset.AssetNotFoundException;
|
import com.jme3.asset.AssetNotFoundException;
|
||||||
import com.jme3.asset.BlenderKey;
|
import com.jme3.asset.BlenderKey;
|
||||||
@ -53,20 +49,23 @@ import com.jme3.asset.BlenderKey.FeaturesToLoad;
|
|||||||
import com.jme3.asset.GeneratedTextureKey;
|
import com.jme3.asset.GeneratedTextureKey;
|
||||||
import com.jme3.asset.TextureKey;
|
import com.jme3.asset.TextureKey;
|
||||||
import com.jme3.math.Vector2f;
|
import com.jme3.math.Vector2f;
|
||||||
import com.jme3.math.Vector3f;
|
|
||||||
import com.jme3.scene.VertexBuffer.Type;
|
import com.jme3.scene.VertexBuffer.Type;
|
||||||
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext;
|
import com.jme3.scene.plugins.blender.BlenderContext;
|
||||||
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
|
||||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||||
|
import com.jme3.scene.plugins.blender.file.DynamicArray;
|
||||||
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
import com.jme3.scene.plugins.blender.file.FileBlockHeader;
|
||||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||||
import com.jme3.scene.plugins.blender.file.Structure;
|
import com.jme3.scene.plugins.blender.file.Structure;
|
||||||
|
import com.jme3.scene.plugins.blender.materials.MaterialContext;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.blending.TextureBlender;
|
||||||
|
import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory;
|
||||||
import com.jme3.scene.plugins.blender.textures.generating.TextureGeneratorFactory;
|
import com.jme3.scene.plugins.blender.textures.generating.TextureGeneratorFactory;
|
||||||
import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
|
import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
|
||||||
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
|
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
|
||||||
import com.jme3.texture.Image;
|
import com.jme3.texture.Image;
|
||||||
import com.jme3.texture.Image.Format;
|
|
||||||
import com.jme3.texture.Texture;
|
import com.jme3.texture.Texture;
|
||||||
import com.jme3.texture.Texture.MinFilter;
|
import com.jme3.texture.Texture.MinFilter;
|
||||||
import com.jme3.texture.Texture.WrapMode;
|
import com.jme3.texture.Texture.WrapMode;
|
||||||
@ -205,280 +204,6 @@ public class TextureHelper extends AbstractBlenderHelper {
|
|||||||
return 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<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(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<DDSTexelData> texelDataList = new ArrayList<DDSTexelData>(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
|
* This class returns a texture read from the file or from packed blender
|
||||||
* data.
|
* data.
|
||||||
@ -798,8 +523,95 @@ public class TextureHelper extends AbstractBlenderHelper {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<Number, CombinedTexture> readTextureData(Structure structure, float[] diffuseColorArray, boolean skyTexture) throws BlenderFileException {
|
||||||
|
DynamicArray<Pointer> mtexsArray = (DynamicArray<Pointer>) structure.getFieldValue("mtex");
|
||||||
|
int separatedTextures = skyTexture ? 0 : ((Number) structure.getFieldValue("septex")).intValue();
|
||||||
|
List<TextureData> texturesList = new ArrayList<TextureData>();
|
||||||
|
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 = skyTexture ? UVCoordinatesType.TEXCO_ORCO.blenderValue : ((Number) textureData.mtex.getFieldValue("texco")).intValue();
|
||||||
|
textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue();
|
||||||
|
textureData.uvCoordinatesName = textureData.mtex.getFieldValue("uvName").toString();
|
||||||
|
if(textureData.uvCoordinatesName != null && textureData.uvCoordinatesName.trim().length() == 0) {
|
||||||
|
textureData.uvCoordinatesName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Number, List<TextureData>> textureDataMap = this.sortTextures(texturesList);
|
||||||
|
Map<Number, CombinedTexture> loadedTextures = new HashMap<Number, CombinedTexture>();
|
||||||
|
for (Entry<Number, List<TextureData>> entry : textureDataMap.entrySet()) {
|
||||||
|
if (entry.getValue().size() > 0) {
|
||||||
|
CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue(), !skyTexture);
|
||||||
|
for (TextureData textureData : entry.getValue()) {
|
||||||
|
int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue();
|
||||||
|
boolean negateTexture = (texflag & 0x04) != 0;
|
||||||
|
Texture texture = this.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, textureData.uvCoordinatesName, blenderContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (combinedTexture.getTexturesCount() > 0) {
|
||||||
|
loadedTextures.put(entry.getKey(), combinedTexture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loadedTextures;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method sorts the textures by their mapping type. In each group only
|
||||||
|
* textures of one type are put (either two- or three-dimensional).
|
||||||
|
*
|
||||||
|
* @return a map with sorted textures
|
||||||
|
*/
|
||||||
|
private Map<Number, List<TextureData>> sortTextures(List<TextureData> textures) {
|
||||||
|
int[] mappings = new int[] { MaterialContext.MTEX_COL, MaterialContext.MTEX_NOR, MaterialContext.MTEX_EMIT, MaterialContext.MTEX_SPEC, MaterialContext.MTEX_ALPHA, MaterialContext.MTEX_AMB };
|
||||||
|
Map<Number, List<TextureData>> result = new HashMap<Number, List<TextureData>>();
|
||||||
|
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<TextureData> datas = result.get(mappings[i]);
|
||||||
|
if (datas == null) {
|
||||||
|
datas = new ArrayList<TextureData>();
|
||||||
|
result.put(mappings[i], datas);
|
||||||
|
}
|
||||||
|
datas.add(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
|
public boolean shouldBeLoaded(Structure structure, BlenderContext blenderContext) {
|
||||||
return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0;
|
return (blenderContext.getBlenderKey().getFeaturesToLoad() & FeaturesToLoad.TEXTURES) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class TextureData {
|
||||||
|
public Structure mtex;
|
||||||
|
public Structure textureStructure;
|
||||||
|
public int uvCoordinatesType;
|
||||||
|
public int projectionType;
|
||||||
|
/** The name of the user's UV coordinates that are used for this texture. */
|
||||||
|
public String uvCoordinatesName;
|
||||||
|
}
|
||||||
}
|
}
|
@ -276,6 +276,20 @@ public class TexturePixel implements Cloneable {
|
|||||||
this.alpha = (this.alpha + pixel.alpha) * 0.5f;
|
this.alpha = (this.alpha + pixel.alpha) * 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mixes two pixels.
|
||||||
|
*
|
||||||
|
* @param pixel
|
||||||
|
* the pixel we mix with
|
||||||
|
*/
|
||||||
|
public void mix(TexturePixel pixel) {
|
||||||
|
this.red = 0.5f * (this.red + pixel.red);
|
||||||
|
this.green = 0.5f * (this.green + pixel.green);
|
||||||
|
this.blue = 0.5f * (this.blue + pixel.blue);
|
||||||
|
this.alpha = 0.5f * (this.alpha + pixel.alpha);
|
||||||
|
this.intensity = 0.5f * (this.intensity + pixel.intensity);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method negates the colors.
|
* This method negates the colors.
|
||||||
*/
|
*/
|
||||||
@ -307,6 +321,19 @@ public class TexturePixel implements Cloneable {
|
|||||||
this.intensity += pixel.intensity;
|
this.intensity += pixel.intensity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method adds the calues of the given pixel to the current pixel.
|
||||||
|
*
|
||||||
|
* @param pixel
|
||||||
|
* the pixel we add
|
||||||
|
*/
|
||||||
|
public void add(ColorRGBA pixel) {
|
||||||
|
this.red += pixel.r;
|
||||||
|
this.green += pixel.g;
|
||||||
|
this.blue += pixel.b;
|
||||||
|
this.alpha += pixel.a;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method multiplies the values of the given pixel by the given value.
|
* This method multiplies the values of the given pixel by the given value.
|
||||||
*
|
*
|
||||||
|
@ -168,33 +168,6 @@ import com.jme3.util.BufferUtils;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* 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
|
* it was not created before. Images that are identical are discarded to
|
||||||
|
Loading…
x
Reference in New Issue
Block a user