0

I would like to place JPEG texture map on sphere. It works for me, but I want to rotate texture by 180 degrees. I.e I want image to start not from zero UV coordinates, but earlier.

enter image description here

UPDATE

I have tried to reassign texture coordinates of a sphere. Texture coordinates are float, and I was hoping they are not constrained to the range of [0..1]. Otherwise it should placed my image into the region of [0..1 x 0..1].

It did something like latter, but not precise:

enter image description here

I.e. entire image was put into small region of a sphere. But, this exact region, where it is located, corresponds with negative values of U, i.e. at the same longitude, where image margin was in previous experiment (top sphere).

Why?

Image is here: https://en.wikipedia.org/wiki/File:Equirectangular_projection_SW.jpg

The code is follows:

package tests.com.jme3;

import java.nio.FloatBuffer;

import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.VertexBuffer.Usage;
import com.jme3.scene.shape.Sphere;
import com.jme3.util.BufferUtils;

public class Try_TextureTransform  extends SimpleApplication {

    public static void main(String[] args) {
        Try_TextureTransform app = new Try_TextureTransform();
        app.setShowSettings(false);
        app.start(); // start the game
    }

    final float speed = 0.01f;

    BitmapText hudText;
    Sphere sphere1Mesh, sphere2Mesh;
    Material sphere1Mat, sphere2Mat;
    Geometry sphere1Geo, sphere2Geo;
    Quaternion orientation;
    DirectionalLight sun;

    @Override
    public void simpleInitApp() {

        flyCam.setEnabled(false);
        setDisplayStatView(false); 
        setDisplayFps(false);


        hudText = new BitmapText(guiFont, false);          
        hudText.setSize(guiFont.getCharSet().getRenderedSize());      // font size
        hudText.setColor(ColorRGBA.Blue);                             // font color
        hudText.setText("");             // the text
        hudText.setLocalTranslation(300, hudText.getLineHeight()*2, 0); // position
        guiNode.attachChild(hudText);

        sphere1Mesh = new Sphere(50, 50, 2);
        sphere1Mesh.setTextureMode(Sphere.TextureMode.Projected); // matrc

        sphere1Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        sphere1Mat.setTexture("ColorMap", assetManager.loadTexture("textures/Equirectangular_projection_SW.jpg"));

        sphere1Geo = new Geometry("Sphere2", sphere1Mesh);
        sphere1Geo.setMaterial(sphere1Mat); 
        sphere1Geo.setLocalTranslation(0, 0, 2);

        sphere2Mesh = new Sphere(50, 50, 2);

        VertexBuffer vb = sphere2Mesh.getBuffer(Type.Position);
        FloatBuffer fb = (FloatBuffer) vb.getData();
        float[] vertexCoordinates = BufferUtils.getFloatArray(fb);

        VertexBuffer vb2 = sphere2Mesh.getBuffer(Type.TexCoord);
        FloatBuffer fb2 = (FloatBuffer) vb2.getData();
        float[] uvCoordinates = BufferUtils.getFloatArray(fb2);

        double rho;
        for (int i = 0; i < vertexCoordinates.length/3; ++i) {

            uvCoordinates[i*2] = (float) Math.atan2(vertexCoordinates[i*3+1], vertexCoordinates[i*3]);
            rho = Math.sqrt(Math.pow( vertexCoordinates[i*3], 2) + Math.pow( vertexCoordinates[i*3+1], 2));
            uvCoordinates[i*2+1] = (float) Math.atan2(vertexCoordinates[i*3+2], rho);
        }
      //apply new texture coordinates
        VertexBuffer uvCoordsBuffer = new VertexBuffer(Type.TexCoord);
        uvCoordsBuffer.setupData(Usage.Static, 2, com.jme3.scene.VertexBuffer.Format.Float, BufferUtils.createFloatBuffer(uvCoordinates));
        sphere2Mesh.clearBuffer(Type.TexCoord);
        sphere2Mesh.setBuffer(uvCoordsBuffer);


        //sphere2Mesh.setTextureMode(Sphere.TextureMode.Projected); // better quality on spheres

        sphere2Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        sphere2Mat.setTexture("ColorMap", assetManager.loadTexture("textures/Equirectangular_projection_SW.jpg"));

        sphere2Geo = new Geometry("Sphere2", sphere2Mesh);
        sphere2Geo.setMaterial(sphere2Mat); 
        sphere2Geo.setLocalTranslation(0, 0, -2);

        cam.setLocation(new Vector3f(-10, 0, 0));
        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Z);

        rootNode.attachChild(sphere1Geo);
        rootNode.attachChild(sphere2Geo); 

    }

    @Override
    public void simpleUpdate(float tpf) {


        Vector2f cursorPosition = inputManager.getCursorPosition();
        Vector3f cursorPositionWorld = cam.getWorldCoordinates(cursorPosition, 1);

        orientation = new Quaternion().fromAngleAxis(cursorPositionWorld.z*speed, Vector3f.UNIT_Y);
        orientation.multLocal(new Quaternion().fromAngleAxis(-cursorPositionWorld.y*speed, Vector3f.UNIT_Z));

        rootNode.setLocalRotation(orientation);



    }

}
Suzan Cioc
  • 29,281
  • 63
  • 213
  • 385
  • Probably would be much easier to rotate the sphere itself or make the texture consistent with your UV coordinates. UV coordinates define where the texture is "pinned" to the shape so if you were even able to do this it would be a nasty hack – Richard Tingle May 18 '14 at 13:58
  • Coordinates are pinned, but why should image be pinned too? – Suzan Cioc May 18 '14 at 14:31
  • O, so you mean you want to create a new image from the old image within Java? Certainly possible but much easier to do outside of Java if you're only doing it once. You could do it in 30 seconds in any image manipulation program – Richard Tingle May 18 '14 at 14:46
  • I can't do it precise in graphics editor (we are speaking about maps). Also, I know it is possible from Java but would like to know, if it is possible within JME. – Suzan Cioc May 18 '14 at 14:50
  • JME is a library on top of Java (I know you know that, I'm emphasising), it would be a waste of their time to replicate core functionality – Richard Tingle May 18 '14 at 14:51
  • So, you mean it is neither possible to redefine default UV coordinates nor possible to apply simple linear transformations while creating textures or applying them onto objects in JME3? – Suzan Cioc May 18 '14 at 14:52
  • P.S. BTW, my image has size 2058x1036, I have downloaded it from Internet. How did it happen, that this image precisely coincided by size with UV coordinates range on default sphere in JME3 library? – Suzan Cioc May 18 '14 at 14:57
  • O, its certainly possible. You would need to write your own vertex shader (piece of code that runs on the graphics card) and copy the fragment shader within `Unshaded.j3md`. The vertex shader would then translate the true texcoords to your translated ones. As vertex shaders go that wouldn't be too hard. But vertex shaders themselves are *really* advanced and intended for much more significant effects. All in all; doable but much more difficult; unnessissarily so. Alternatively you could create your own custom mesh where you manually specify the texcoord buffer (also a hassel) – Richard Tingle May 18 '14 at 15:00
  • What about second question? Why sphere UV coordinate range is 2058x1036? – Suzan Cioc May 18 '14 at 15:02
  • UV coordinates are between 0 and 1. They don't care about the resolution of the image – Richard Tingle May 18 '14 at 15:02
  • If the range is [0..1] then only 1x1 pixel images can fit onto it without linear transformations. – Suzan Cioc May 18 '14 at 15:03
  • Yeah, there are **loads** of transformations going on "under the hood", but if you want to alter them you have to get into shader design – Richard Tingle May 18 '14 at 15:13

1 Answers1

4

The correct way to do this is just to rotate the geometry as you see fit or edit the texture (techniques 1 and 2) but because you talk about modifying the texture coordinates themselves I include techniques 3 and 4 in case you are using this example to learn a larger technique for when it is appropriate.

Technique 1 - Rotate the geometry

Rotate the geometry so that it is orientated the way you want it. This is by far the easiest, most appropriate and most understandable technique and what I recommend

    //Add this
    Quaternion quat=new Quaternion();
    quat.fromAngles(0 ,0 , FastMath.PI);
    sphere1Geo.setLocalRotation(quat);

enter image description here

Complete program

public class Main extends SimpleApplication {

    public static void main(String[] args) {
        Main app = new Main();
        app.setShowSettings(false);
        app.start(); // start the game
    }

    final float speed = 0.01f;

    BitmapText hudText;
    Quaternion orientation;
    DirectionalLight sun;

    @Override
    public void simpleInitApp() {

        flyCam.setEnabled(false);
        setDisplayStatView(false); 
        setDisplayFps(false);


        hudText = new BitmapText(guiFont, false);          
        hudText.setSize(guiFont.getCharSet().getRenderedSize());      // font size
        hudText.setColor(ColorRGBA.Blue);                             // font color
        hudText.setText("");             // the text
        hudText.setLocalTranslation(300, hudText.getLineHeight()*2, 0); // position
        guiNode.attachChild(hudText);

        cam.setLocation(new Vector3f(10, 0, 0));
        cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Z);

        addOriginalSphere();
        addRotatedSphere();

    }

    public void addOriginalSphere(){
        Sphere sphere1Mesh = new Sphere(50, 50, 2);
        sphere1Mesh.setTextureMode(Sphere.TextureMode.Projected); // matrc

        Material sphere1Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        sphere1Mat.setTexture("ColorMap", assetManager.loadTexture("Textures/world.png"));

        Geometry sphere1Geo = new Geometry("Original Sphere", sphere1Mesh);
        sphere1Geo.setMaterial(sphere1Mat); 
        sphere1Geo.setLocalTranslation(0, -2, 0);

        rootNode.attachChild(sphere1Geo);
    }
    public void addRotatedSphere(){
        Sphere sphere1Mesh = new Sphere(50, 50, 2);
        sphere1Mesh.setTextureMode(Sphere.TextureMode.Projected); // matrc

        Material sphere1Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        sphere1Mat.setTexture("ColorMap", assetManager.loadTexture("Textures/world.png"));

        Geometry sphere1Geo = new Geometry("Rotated Sphere", sphere1Mesh);
        sphere1Geo.setMaterial(sphere1Mat); 
        sphere1Geo.setLocalTranslation(0, 2, 0);

        //Add this
        Quaternion quat=new Quaternion();
        quat.fromAngles(0 ,0 , FastMath.PI);
        sphere1Geo.setLocalRotation(quat);

        rootNode.attachChild(sphere1Geo);
    }

    @Override
    public void simpleUpdate(float tpf) {



    }

}

Technique 2 - Edit the texture to conform to the way you want it to be

Many image editing programs exist, the one I use is Paint.Net and (like most editing software) gives exact pixel mouse coordinates. Just cut and paste the image such that greenwich is at the far left. In your case you need to edit the image anyway because it has that horrible white border on it.

Technique 3 - Mess with the vertex texture co-ordinates

This is overkill for this and is not what I recomend. But if this is an excercise to learn to create your own custom mesh then read on

public void addRotatedSphere_ByMessingWithMesh(){
    Sphere sphere1Mesh = new Sphere(50, 50, 2);
    sphere1Mesh.setTextureMode(Sphere.TextureMode.Projected); // matrc


    FloatBuffer textureBuffer=sphere1Mesh.getFloatBuffer(Type.TexCoord);

    float[] newTextureCoordinates=new float[textureBuffer.capacity()];


    for(int i=0;i<newTextureCoordinates.length;i++){
        //texture buffer goes x co-ordinate, y coordinate, x coordinate, y coordinate
        if (i%2!=1){
            newTextureCoordinates[i]=(float)((textureBuffer.get(i)+0.5)%1);
        }else{
            newTextureCoordinates[i]=textureBuffer.get(i);
        }
    }

    sphere1Mesh.setBuffer(Type.TexCoord, 2,newTextureCoordinates);

    Material sphere1Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    sphere1Mat.setTexture("ColorMap", assetManager.loadTexture("Textures/world.png"));



    Geometry sphere1Geo = new Geometry("Rotated Sphere", sphere1Mesh);
    sphere1Geo.setMaterial(sphere1Mat); 
    sphere1Geo.setLocalTranslation(0, 2, 0);



    rootNode.attachChild(sphere1Geo);
}

enter image description here

This has a problem because the seam at the back is not done properly; because the true texture coordinates go 0,0.2,0.4,0.8,1. Whereas out new ones do a wrap around on the far side. In this specific example you can do a manual handling of the seam but you can already see that this is a pain.

Technique 4 - Write your own shader

This is bordering on rediculus but you could write a custom shader that would take the true texture coordinates and apply a transformation similar to the one performed within Technique 3, but this would be done on the graphics card and is a nightmare to debug.

It goes without saying that that would be using a small nuclear weapon to kill a fly and I shall not explain all the step explicity (but its heavily based on unshaded.j3md and unshaded.vert

  • Create the following files to define our new material

enter image description here

Material definition

Only change is to mention our custom vertex shader rather than use the custom one

MaterialDef Unshaded {

    MaterialParameters {
        Texture2D ColorMap
        Texture2D LightMap
        Color Color (Color)
        Boolean VertexColor (UseVertexColor)
        Boolean SeparateTexCoord

        // Texture of the glowing parts of the material
        Texture2D GlowMap
        // The glow color of the object
        Color GlowColor

        // For hardware skinning
        Int NumberOfBones
        Matrix4Array BoneMatrices

        // Alpha threshold for fragment discarding
        Float AlphaDiscardThreshold (AlphaTestFallOff)

        //Shadows
        Int FilterMode
        Boolean HardwareShadows

        Texture2D ShadowMap0
        Texture2D ShadowMap1
        Texture2D ShadowMap2
        Texture2D ShadowMap3
        //pointLights
        Texture2D ShadowMap4
        Texture2D ShadowMap5

        Float ShadowIntensity
        Vector4 Splits
        Vector2 FadeInfo

        Matrix4 LightViewProjectionMatrix0
        Matrix4 LightViewProjectionMatrix1
        Matrix4 LightViewProjectionMatrix2
        Matrix4 LightViewProjectionMatrix3
        //pointLight
        Matrix4 LightViewProjectionMatrix4
        Matrix4 LightViewProjectionMatrix5
        Vector3 LightPos
        Vector3 LightDir

        Float PCFEdge

        Float ShadowMapSize
    }

    Technique {
        VertexShader GLSL100:   MatDefs/TextureSplitting.vert
        FragmentShader GLSL100: Common/MatDefs/Misc/Unshaded.frag

        WorldParameters {
            WorldViewProjectionMatrix
        }

        Defines {
            SEPARATE_TEXCOORD : SeparateTexCoord
            HAS_COLORMAP : ColorMap
            HAS_LIGHTMAP : LightMap
            HAS_VERTEXCOLOR : VertexColor
            HAS_COLOR : Color
            NUM_BONES : NumberOfBones
            DISCARD_ALPHA : AlphaDiscardThreshold
        }
    }

    Technique {
    }

    Technique PreNormalPass {

          VertexShader GLSL100 :   Common/MatDefs/SSAO/normal.vert
          FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag

          WorldParameters {
              WorldViewProjectionMatrix
              WorldViewMatrix
              NormalMatrix
          }

          Defines {
              NUM_BONES : NumberOfBones
          }
   }

    Technique PreShadow {

        VertexShader GLSL100 :   Common/MatDefs/Shadow/PreShadow.vert
        FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag

        WorldParameters {
            WorldViewProjectionMatrix
            WorldViewMatrix
        }

        Defines {
            COLOR_MAP : ColorMap
            DISCARD_ALPHA : AlphaDiscardThreshold
            NUM_BONES : NumberOfBones
        }

        ForcedRenderState {
            FaceCull Off
            DepthTest On
            DepthWrite On
            PolyOffset 5 3
            ColorWrite Off
        }

    }


    Technique PostShadow15{
        VertexShader GLSL150:   Common/MatDefs/Shadow/PostShadow15.vert
        FragmentShader GLSL150: Common/MatDefs/Shadow/PostShadow15.frag

        WorldParameters {
            WorldViewProjectionMatrix
            WorldMatrix
        }

        Defines {
            HARDWARE_SHADOWS : HardwareShadows
            FILTER_MODE : FilterMode
            PCFEDGE : PCFEdge
            DISCARD_ALPHA : AlphaDiscardThreshold           
            COLOR_MAP : ColorMap
            SHADOWMAP_SIZE : ShadowMapSize
            FADE : FadeInfo
            PSSM : Splits
            POINTLIGHT : LightViewProjectionMatrix5
            NUM_BONES : NumberOfBones
        }

        ForcedRenderState {
            Blend Modulate
            DepthWrite Off                 
            PolyOffset -0.1 0
        }
    }

    Technique PostShadow{
        VertexShader GLSL100:   Common/MatDefs/Shadow/PostShadow.vert
        FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag

        WorldParameters {
            WorldViewProjectionMatrix
            WorldMatrix
        }

        Defines {
            HARDWARE_SHADOWS : HardwareShadows
            FILTER_MODE : FilterMode
            PCFEDGE : PCFEdge
            DISCARD_ALPHA : AlphaDiscardThreshold           
            COLOR_MAP : ColorMap
            SHADOWMAP_SIZE : ShadowMapSize
            FADE : FadeInfo
            PSSM : Splits
            POINTLIGHT : LightViewProjectionMatrix5
            NUM_BONES : NumberOfBones
        }

        ForcedRenderState {
            Blend Modulate
            DepthWrite Off   
            PolyOffset -0.1 0  
        }
    }

    Technique Glow {

        VertexShader GLSL100:   Common/MatDefs/Misc/TextureSplitting.vert
        FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag

        WorldParameters {
            WorldViewProjectionMatrix
        }

        Defines {
            NEED_TEXCOORD1
            HAS_GLOWMAP : GlowMap
            HAS_GLOWCOLOR : GlowColor
            NUM_BONES : NumberOfBones
        }
    }
}

Vertex shader

Use a translation to map the true texture coordinates to the shifted coordinates. Incidently if you think this isn't java; it isn't. Its OpenGL Shader Langauge.

#import "Common/ShaderLib/Skinning.glsllib"

uniform mat4 g_WorldViewProjectionMatrix;
attribute vec3 inPosition;

#if defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD))
    #define NEED_TEXCOORD1
#endif

attribute vec2 inTexCoord;
attribute vec2 inTexCoord2;
attribute vec4 inColor;

varying vec2 texCoord1;
varying vec2 texCoord2;

varying vec4 vertColor;

void main(){
    #ifdef NEED_TEXCOORD1
        texCoord1 = inTexCoord;
        texCoord1.x=texCoord1.x+0.5;
        if (texCoord1.x>1){
            texCoord1.x=texCoord1.x-1;
        }
    #endif

    #ifdef SEPARATE_TEXCOORD
        texCoord2 = inTexCoord2;
    #endif

    #ifdef HAS_VERTEXCOLOR
        vertColor = inColor;
    #endif

    vec4 modelSpacePos = vec4(inPosition, 1.0);
    #ifdef NUM_BONES
        Skinning_Compute(modelSpacePos);
    #endif
    gl_Position = g_WorldViewProjectionMatrix * modelSpacePos;
}

Then use this as a material instead of unshaded.j3md

Material sphere1Mat = new Material(assetManager, "Materials/TextureSplitting.j3md");

enter image description here

Again there is a nasty break around the back where the true texture roles over between 0 and 1 which we could handle explicitly if we wanted but we'd have to make sure there were 2 vertexs at the split point one with texture coordinate 0 and one with texture coordinate 1.

Conclusion

Techniques 1 or 2 are the ones you should use. I include techniques 3 and 4 simply to show that you can do this using the actual texture coordinates but that you shouldn't.

Richard Tingle
  • 16,906
  • 5
  • 52
  • 77
  • Thank you for full answer. But only way 3 is interesting for me as an answer to original question. Also special thanks for way 4 -- it is not needed right now, but interesting for future. I can't utilize ways 1 and 2 because the question contains just SSCCE. In full task I am generating custom meshes and prefer coordinates most close to correct. – Suzan Cioc May 19 '14 at 12:27
  • @SuzanCioc Cool, regarding the nasty seam where the texture "wraps round" from 1 to 0. You will need special handling for that; as it stands the mesh cannot avoid the seam at all but If you have control of the mesh then you need to double the number of verticies at the seam point and have one at coordinate 0 and one at 1. This eliminates the seam. If you have trouble with that ask annother question but I'd need to see the specifics of how you're forming the mesh to answer that – Richard Tingle May 19 '14 at 12:30