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
experimental
Kae..pl 12 years ago
parent 535a6dd8ce
commit eda3e8d725
  1. 82
      engine/src/blender/com/jme3/asset/BlenderKey.java
  2. 2
      engine/src/blender/com/jme3/scene/plugins/blender/textures/CombinedTexture.java
  3. 79
      engine/src/blender/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java

@ -111,6 +111,10 @@ public class BlenderKey extends ModelKey {
* textures will get their proper size. * 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. * Constructor used by serialization mechanisms.
@ -267,7 +271,7 @@ public class BlenderKey extends ModelKey {
* bitwise flag of FeaturesToLoad interface values * bitwise flag of FeaturesToLoad interface values
*/ */
public void excludeFromLoading(int featuresNotToLoad) { public void excludeFromLoading(int featuresNotToLoad) {
this.featuresToLoad &= ~featuresNotToLoad; featuresToLoad &= ~featuresNotToLoad;
} }
/** /**
@ -379,6 +383,39 @@ public class BlenderKey extends ModelKey {
this.skyGeneratedTextureSize = skyGeneratedTextureSize; 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 * 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
@ -430,6 +467,9 @@ public class BlenderKey extends ModelKey {
oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off); oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off);
oc.write(layersToLoad, "layers-to-load", -1); oc.write(layersToLoad, "layers-to-load", -1);
oc.write(mipmapGenerationMethod, "mipmap-generation-method", MipmapGenerationMethod.GENERATE_WHEN_NEEDED); 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 @Override
@ -447,6 +487,9 @@ public class BlenderKey extends ModelKey {
faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off); faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off);
layersToLoad = ic.readInt("layers-to=load", -1); layersToLoad = ic.readInt("layers-to=load", -1);
mipmapGenerationMethod = ic.readEnum("mipmap-generation-method", MipmapGenerationMethod.class, MipmapGenerationMethod.GENERATE_WHEN_NEEDED); 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 @Override
@ -460,8 +503,15 @@ public class BlenderKey extends ModelKey {
result = prime * result + (fixUpAxis ? 1231 : 1237); result = prime * result + (fixUpAxis ? 1231 : 1237);
result = prime * result + fps; result = prime * result + fps;
result = prime * result + generatedTexturePPU; result = prime * result + generatedTexturePPU;
result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode());
result = prime * result + layersToLoad; 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 + (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()); result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode());
return result; return result;
} }
@ -507,12 +557,33 @@ public class BlenderKey extends ModelKey {
if (generatedTexturePPU != other.generatedTexturePPU) { if (generatedTexturePPU != other.generatedTexturePPU) {
return false; return false;
} }
if (skyGeneratedTextureShape != other.skyGeneratedTextureShape) {
return false;
}
if (layersToLoad != other.layersToLoad) { if (layersToLoad != other.layersToLoad) {
return false; return false;
} }
if (loadGeneratedTextures != other.loadGeneratedTextures) {
return false;
}
if (loadObjectProperties != other.loadObjectProperties) {
return false;
}
if (loadUnlinkedAssets != other.loadUnlinkedAssets) { if (loadUnlinkedAssets != other.loadUnlinkedAssets) {
return false; 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 (usedWorld == null) {
if (other.usedWorld != null) { if (other.usedWorld != null) {
return false; return false;
@ -548,6 +619,15 @@ public class BlenderKey extends ModelKey {
int ALL = 0xFFFFFFFF; 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. * This class holds the loading results according to the given loading flag.
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)

@ -269,7 +269,7 @@ public class CombinedTexture {
for (TextureData textureData : textureDatas) { for (TextureData textureData : textureDatas) {
TextureCubeMap texture = null; TextureCubeMap texture = null;
if (textureData.texture instanceof GeneratedTexture) { if (textureData.texture instanceof GeneratedTexture) {
texture = ((GeneratedTexture) textureData.texture).generateSkyTexture(size, horizontalColor, zenithColor); texture = ((GeneratedTexture) textureData.texture).generateSkyTexture(size, horizontalColor, zenithColor, blenderContext);
} else { } else {
// first create a grayscale version of the image // first create a grayscale version of the image
Image image = textureData.texture.getImage(); Image image = textureData.texture.getImage();

@ -23,6 +23,7 @@ import com.jme3.texture.Image;
import com.jme3.texture.Image.Format; import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture; import com.jme3.texture.Texture;
import com.jme3.texture.TextureCubeMap; import com.jme3.texture.TextureCubeMap;
import com.jme3.util.TempVars;
/** /**
* The generated texture loaded from blender file. The texture is not generated * The generated texture loaded from blender file. The texture is not generated
@ -55,6 +56,49 @@ import com.jme3.texture.TextureCubeMap;
private final Structure mTex; private final Structure mTex;
/** Texture generateo for the specified texture type. */ /** 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. * Constructor. Reads the required data from the 'tex' structure.
@ -137,37 +181,48 @@ import com.jme3.texture.TextureCubeMap;
* the horizon color * the horizon color
* @param zenithColor * @param zenithColor
* the zenith color * the zenith color
* @param blenderContext
* the blender context
* @return the sky texture * @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); Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6);
PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat());
TexturePixel pixel = new TexturePixel(); TexturePixel pixel = new TexturePixel();
float delta = 1 / (float) (size - 1); float delta = 1 / (float) (size - 1);
float sideV, sideS = 1, forwardU = 1, forwardV, upS; 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) { for (int x = 0; x < size; ++x) {
sideV = 1; sideV = 1;
forwardV = 1; forwardV = 1;
upS = 0; upS = 0;
for (int y = 0; y < size; ++y) { 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 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 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 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 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 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 pixelIO.write(image, POSITIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// bottom
sideV = FastMath.clamp(sideV - delta, 0, 1); sideV = FastMath.clamp(sideV - delta, 0, 1);
@ -177,6 +232,7 @@ import com.jme3.texture.TextureCubeMap;
sideS = FastMath.clamp(sideS - delta, 0, 1); sideS = FastMath.clamp(sideS - delta, 0, 1);
forwardU = FastMath.clamp(forwardU - delta, 0, 1); forwardU = FastMath.clamp(forwardU - delta, 0, 1);
} }
tempVars.release();
return new TextureCubeMap(image); return new TextureCubeMap(image);
} }
@ -214,4 +270,13 @@ import com.jme3.texture.TextureCubeMap;
super.format = imageFormat; 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);
}
} }

Loading…
Cancel
Save