From eda3e8d725bc30f8256a6b91f250900f12af5b99 Mon Sep 17 00:00:00 2001 From: "Kae..pl" Date: Sat, 5 Oct 2013 16:29:12 +0000 Subject: [PATCH] Feature: sky generated textures can be generated agains a cube or a sphere of a selected size. By default a sphere is now used. This makes the sky look entirely seamless. git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10819 75d07b2b-3a1a-0410-a2c5-0572b91ccdca --- .../blender/com/jme3/asset/BlenderKey.java | 114 ++++++++++++++--- .../blender/textures/CombinedTexture.java | 2 +- .../blender/textures/GeneratedTexture.java | 115 ++++++++++++++---- 3 files changed, 188 insertions(+), 43 deletions(-) diff --git a/engine/src/blender/com/jme3/asset/BlenderKey.java b/engine/src/blender/com/jme3/asset/BlenderKey.java index 151cd4d38..da4c26301 100644 --- a/engine/src/blender/com/jme3/asset/BlenderKey.java +++ b/engine/src/blender/com/jme3/asset/BlenderKey.java @@ -63,54 +63,58 @@ import com.jme3.texture.Texture; */ 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 * 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. */ - 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. */ - protected boolean loadUnlinkedAssets; + protected boolean loadUnlinkedAssets; /** 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. */ - protected boolean fixUpAxis = true; + protected boolean fixUpAxis = true; /** 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 * then the first world settings in the file will be used. */ - protected String usedWorld; + protected String usedWorld; /** * User's default material that is set fo objects that have no material definition in blender. The default value is * null. If the value is null the importer will use its own default material (gray color - like in blender). */ - protected Material defaultMaterial; + protected Material defaultMaterial; /** 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. * 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. */ - protected boolean loadObjectProperties = true; + protected boolean loadObjectProperties = true; /** 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. */ - 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. */ - 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; + protected int skyGeneratedTextureSize = 1000; + /** The radius of a shape that will be used while creating the generated texture for the sky. The higher it is the larger part of the texture will be seen. */ + protected float skyGeneratedTextureRadius = 1; + /** The shape against which the generated texture for the sky will be created. */ + protected SkyGeneratedTextureShape skyGeneratedTextureShape = SkyGeneratedTextureShape.SPHERE; /** * Constructor used by serialization mechanisms. @@ -267,7 +271,7 @@ public class BlenderKey extends ModelKey { * bitwise flag of FeaturesToLoad interface values */ public void excludeFromLoading(int featuresNotToLoad) { - this.featuresToLoad &= ~featuresNotToLoad; + featuresToLoad &= ~featuresNotToLoad; } /** @@ -379,6 +383,39 @@ public class BlenderKey extends ModelKey { this.skyGeneratedTextureSize = skyGeneratedTextureSize; } + /** + * @return the radius of a shape that will be used while creating the generated texture for the sky, the higher it is the larger part of the texture will be seen + */ + public float getSkyGeneratedTextureRadius() { + return skyGeneratedTextureRadius; + } + + /** + * @param skyGeneratedTextureRadius + * the radius of a shape that will be used while creating the generated texture for the sky, the higher it is the larger part of the texture will be seen + */ + public void setSkyGeneratedTextureRadius(float skyGeneratedTextureRadius) { + this.skyGeneratedTextureRadius = skyGeneratedTextureRadius; + } + + /** + * @return the shape against which the generated texture for the sky will be created (by default it is a sphere). + */ + public SkyGeneratedTextureShape getSkyGeneratedTextureShape() { + return skyGeneratedTextureShape; + } + + /** + * @param skyGeneratedTextureShape + * the shape against which the generated texture for the sky will be created + */ + public void setSkyGeneratedTextureShape(SkyGeneratedTextureShape skyGeneratedTextureShape) { + if(skyGeneratedTextureShape == null) { + throw new IllegalArgumentException("The sky generated shape type cannot be null!"); + } + this.skyGeneratedTextureShape = skyGeneratedTextureShape; + } + /** * 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 @@ -430,6 +467,9 @@ public class BlenderKey extends ModelKey { oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off); oc.write(layersToLoad, "layers-to-load", -1); oc.write(mipmapGenerationMethod, "mipmap-generation-method", MipmapGenerationMethod.GENERATE_WHEN_NEEDED); + oc.write(skyGeneratedTextureSize, "sky-generated-texture-size", 1000); + oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f); + oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE); } @Override @@ -447,6 +487,9 @@ public class BlenderKey extends ModelKey { faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off); layersToLoad = ic.readInt("layers-to=load", -1); mipmapGenerationMethod = ic.readEnum("mipmap-generation-method", MipmapGenerationMethod.class, MipmapGenerationMethod.GENERATE_WHEN_NEEDED); + skyGeneratedTextureSize = ic.readInt("sky-generated-texture-size", 1000); + skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f); + skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE); } @Override @@ -460,8 +503,15 @@ public class BlenderKey extends ModelKey { result = prime * result + (fixUpAxis ? 1231 : 1237); result = prime * result + fps; result = prime * result + generatedTexturePPU; + result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode()); result = prime * result + layersToLoad; + result = prime * result + (loadGeneratedTextures ? 1231 : 1237); + result = prime * result + (loadObjectProperties ? 1231 : 1237); result = prime * result + (loadUnlinkedAssets ? 1231 : 1237); + result = prime * result + maxTextureSize; + result = prime * result + (mipmapGenerationMethod == null ? 0 : mipmapGenerationMethod.hashCode()); + result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius); + result = prime * result + skyGeneratedTextureSize; result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode()); return result; } @@ -507,12 +557,33 @@ public class BlenderKey extends ModelKey { if (generatedTexturePPU != other.generatedTexturePPU) { return false; } + if (skyGeneratedTextureShape != other.skyGeneratedTextureShape) { + return false; + } if (layersToLoad != other.layersToLoad) { return false; } + if (loadGeneratedTextures != other.loadGeneratedTextures) { + return false; + } + if (loadObjectProperties != other.loadObjectProperties) { + return false; + } if (loadUnlinkedAssets != other.loadUnlinkedAssets) { return false; } + if (maxTextureSize != other.maxTextureSize) { + return false; + } + if (mipmapGenerationMethod != other.mipmapGenerationMethod) { + return false; + } + if (Float.floatToIntBits(skyGeneratedTextureRadius) != Float.floatToIntBits(other.skyGeneratedTextureRadius)) { + return false; + } + if (skyGeneratedTextureSize != other.skyGeneratedTextureSize) { + return false; + } if (usedWorld == null) { if (other.usedWorld != null) { return false; @@ -548,6 +619,15 @@ public class BlenderKey extends ModelKey { int ALL = 0xFFFFFFFF; } + /** + * The shape againts which the sky generated texture will be created. + * + * @author Marcin Roguski (Kaelthas) + */ + public static enum SkyGeneratedTextureShape { + CUBE, SPHERE; + } + /** * This class holds the loading results according to the given loading flag. * @author Marcin Roguski (Kaelthas) diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java index d7e62e680..02da754eb 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java @@ -269,7 +269,7 @@ public class CombinedTexture { for (TextureData textureData : textureDatas) { TextureCubeMap texture = null; if (textureData.texture instanceof GeneratedTexture) { - texture = ((GeneratedTexture) textureData.texture).generateSkyTexture(size, horizontalColor, zenithColor); + texture = ((GeneratedTexture) textureData.texture).generateSkyTexture(size, horizontalColor, zenithColor, blenderContext); } else { // first create a grayscale version of the image Image image = textureData.texture.getImage(); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java b/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java index 2e173e796..8202953f2 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java @@ -23,6 +23,7 @@ import com.jme3.texture.Image; import com.jme3.texture.Image.Format; import com.jme3.texture.Texture; import com.jme3.texture.TextureCubeMap; +import com.jme3.util.TempVars; /** * The generated texture loaded from blender file. The texture is not generated @@ -32,29 +33,72 @@ import com.jme3.texture.TextureCubeMap; * @author Marcin Roguski (Kaelthas) */ /* 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; + 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 - public static final int TEX_COLORBAND = 1; - public static final int TEX_FLIPBLEND = 2; - public static final int TEX_NEGALPHA = 4; - public static final int TEX_CHECKER_ODD = 8; - public static final int TEX_CHECKER_EVEN = 16; - public static final int TEX_PRV_ALPHA = 32; - public static final int TEX_PRV_NOR = 64; - public static final int TEX_REPEAT_XMIR = 128; - public static final int TEX_REPEAT_YMIR = 256; - public static final int TEX_FLAG_MASK = TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR; + public static final int TEX_COLORBAND = 1; + public static final int TEX_FLIPBLEND = 2; + public static final int TEX_NEGALPHA = 4; + public static final int TEX_CHECKER_ODD = 8; + public static final int TEX_CHECKER_EVEN = 16; + public static final int TEX_PRV_ALPHA = 32; + public static final int TEX_PRV_NOR = 64; + public static final int TEX_REPEAT_XMIR = 128; + public static final int TEX_REPEAT_YMIR = 256; + public static final int TEX_FLAG_MASK = TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR; /** Material-texture link structure. */ - private final Structure mTex; + private final Structure mTex; /** Texture generateo for the specified texture type. */ - private final TextureGenerator textureGenerator; + private final TextureGenerator textureGenerator; + /** + * The generated texture cast functions. They are used to cas a given point on a plane to a specified shape in 3D space. + * The functions should be ordered as the ordinal of a BlenderKey.CastFunction enums. + */ + private final static CastFunction[] CAST_FUNCTIONS = new CastFunction[] { + /** + * The cube casting function (does nothing except scaling if needed because the given points are already on a cube). + */ + new CastFunction() { + @Override + public void cast(Vector3f pointToCast, float radius) { + //computed using the Thales' theorem + float length = 2 * pointToCast.subtractLocal(0.5f, 0.5f, 0.5f).length() * radius; + pointToCast.normalizeLocal().addLocal(0.5f, 0.5f, 0.5f).multLocal(length); + } + }, + + /** + * The sphere casting function. + */ + new CastFunction() { + /** + * The method casts a point on a plane to a sphere. + * The plane is one of the faces of a cube that has a edge of length 1 and center in (0.5 0.5, 0.5). This cube is a basic 3d area where generated texture + * is created. + * To cast a point on a cube face to a sphere that is inside the cube we perform several easy vector operations. + * 1. create a vector from the cube's center to the point + * 2. setting its length to 0.5 (the radius of the sphere) + * 3. adding the value of the cube's center to get a point on the sphere + * + * The result is stored in the given vector. + * + * @param pointToCast + * the point on a plane that will be cast to a sphere + * @param radius + * the radius of the sphere + */ + @Override + public void cast(Vector3f pointToCast, float radius) { + pointToCast.subtractLocal(0.5f, 0.5f, 0.5f).normalizeLocal().multLocal(radius).addLocal(0.5f, 0.5f, 0.5f); + } + } + }; /** * Constructor. Reads the required data from the 'tex' structure. @@ -137,37 +181,48 @@ import com.jme3.texture.TextureCubeMap; * the horizon color * @param zenithColor * the zenith color + * @param blenderContext + * the blender context * @return the sky texture */ - public TextureCubeMap generateSkyTexture(int size, ColorRGBA horizontalColor, ColorRGBA zenithColor) { + public TextureCubeMap generateSkyTexture(int size, ColorRGBA horizontalColor, ColorRGBA zenithColor, BlenderContext blenderContext) { 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; + TempVars tempVars = TempVars.get(); + CastFunction castFunction = CAST_FUNCTIONS[blenderContext.getBlenderKey().getSkyGeneratedTextureShape().ordinal()]; + float castRadius = blenderContext.getBlenderKey().getSkyGeneratedTextureRadius(); 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); + castFunction.cast(tempVars.vect1.set(1, sideV, sideS), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); pixelIO.write(image, NEGATIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// right - textureGenerator.getPixel(pixel, 0, sideV, 1 - sideS); + castFunction.cast(tempVars.vect1.set(0, sideV, 1 - sideS), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); pixelIO.write(image, POSITIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// left - textureGenerator.getPixel(pixel, forwardU, forwardV, 0); + castFunction.cast(tempVars.vect1.set(forwardU, forwardV, 0), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); pixelIO.write(image, POSITIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// front - textureGenerator.getPixel(pixel, 1 - forwardU, forwardV, 1); + castFunction.cast(tempVars.vect1.set(1 - forwardU, forwardV, 1), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); pixelIO.write(image, NEGATIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// back - textureGenerator.getPixel(pixel, forwardU, 0, upS); + castFunction.cast(tempVars.vect1.set(forwardU, 0, upS), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); pixelIO.write(image, NEGATIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// top - textureGenerator.getPixel(pixel, forwardU, 1, 1 - upS); + castFunction.cast(tempVars.vect1.set(forwardU, 1, 1 - upS), castRadius); + textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); pixelIO.write(image, POSITIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// bottom sideV = FastMath.clamp(sideV - delta, 0, 1); @@ -177,6 +232,7 @@ import com.jme3.texture.TextureCubeMap; sideS = FastMath.clamp(sideS - delta, 0, 1); forwardU = FastMath.clamp(forwardU - delta, 0, 1); } + tempVars.release(); return new TextureCubeMap(image); } @@ -214,4 +270,13 @@ import com.jme3.texture.TextureCubeMap; super.format = imageFormat; } } + + /** + * The casting functions to create a sky generated texture against selected shape of a selected size. + * + * @author Marcin Roguski (Kaelthas) + */ + private static interface CastFunction { + void cast(Vector3f pointToCast, float radius); + } }