[SOLVED] Two Color Sphere

I’m trying to make an artificial horizon for my space ship console. It is a two color sphere with a brown lower hemisphere and a blue upper hemisphere. (I’d like keep colors changeable at runtime so that when you are in space, the bottom would be white and the top black.)
I could 1) make a two color sphere in Blender, but I don’t know if I can change the colors, 2) make a material that takes two colors, (I did the exercises and can make a MatDef), 3) make a ColorMap with two colors. (Would it only need to be 2 pixels in height? Does it get scaled?) 4) I was looking at Shaders…
I like option 2) I’m trying to do it with a material. I can make a MaterialDef that just changes the color of the whole sphere. It seems reasonable to assume that, in the .vert file, I could say:
if (inPosition.y >- 0)
m_Color = topColor
else
m_Color = bottomColor
I haven’t seen any access to the position components nor have I seen any control statements (‘cept #ifdef).

How can I write a MatDef to do that or how else can I do it.

1 Like

Shaders would be better but a super simple way would be to make 2 half-spheres and just change the material on one of them.

1 Like

This sounds like a good exercise for me. Hold my beer…

Oh … do you want a lit/shaded material or an unshaded one?

That I shall. And I’ll hold one for me too.
Unshaded is fine.

1 Like

I can’t find a good closeup and some look flat and not spherical but here is a link.

Here’s what I came up with:

Bicolor.frag:

/*
 * a very simple fragment shader for use with Bicolor.j3md
 */
#import "Common/ShaderLib/GLSLCompat.glsllib"

uniform vec4 m_TopColor;
uniform vec4 m_BottomColor;
varying vec2 texCoord1; // from vertex shader

void main() {
    // Set frag color based on 2nd (V) component of texCoord1.

    // In a com.jme3.scene.shape.Sphere with TextureMode.Original,
    // V increases linearly (from 0 to 1) with the local Z coordinate,
    // so the "horizon" is at V=0.5 .

    if (texCoord1.y >= 0.5) {
        gl_FragColor = m_TopColor;
    } else {
        gl_FragColor = m_BottomColor;
    }
}

Bicolor.vert:

/*
 * a very simple vertex shader which always generates texCoord1
 * (used by Bicolor.j3md)
 */

#import "Common/ShaderLib/GLSLCompat.glsllib"
#import "Common/ShaderLib/Skinning.glsllib"
#import "Common/ShaderLib/Instancing.glsllib"

attribute vec3 inPosition;
attribute vec2 inTexCoord;
varying vec2 texCoord1; // to fragment shader

void main(){
    texCoord1 = inTexCoord;

    vec4 modelSpacePos = vec4(inPosition, 1.0);
    gl_Position = TransformWorldViewProjection(modelSpacePos);
}

Bicolor.j3md:

// material definition for a simple, unshaded material with exactly 2 colors

MaterialDef Bicolor {

    MaterialParameters {
        Color BottomColor (BottomColor)
        Color TopColor (TopColor)
    }

    Technique {
        VertexShader GLSL100:   Shaders/Bicolor.vert
        FragmentShader GLSL100: Shaders/Bicolor.frag

        WorldParameters {
            WorldViewProjectionMatrix
            ViewProjectionMatrix
            ViewMatrix
        }
    }
}

Main.java

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.shape.Sphere;

/*
 * Demo for Bicolor material.
 */
public class Main extends SimpleApplication {

    double elapsedTime = 0f; // seconds
    boolean inSpace = false;
    Material bicolor;

    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
        cam.setLocation(new Vector3f(3f, 1.4f, -3.2f));
        cam.setRotation(new Quaternion(0.15701026f, -0.39636666f, 0.06899879f, 0.90193146f));
        flyCam.setMoveSpeed(5f);

        Mesh ballMesh = new Sphere(40, 40, 1f);
        Geometry ballGeometry = new Geometry("Attitude Indicator Ball", ballMesh);
        rootNode.attachChild(ballGeometry);

        Quaternion upright = new Quaternion();
        upright.fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Z.negate(), Vector3f.UNIT_Y);
        ballGeometry.setLocalRotation(upright);

        bicolor = new Material(assetManager, "MatDefs/Bicolor.j3md");
        ballGeometry.setMaterial(bicolor);
        updateMaterials();
    }

    @Override
    public void simpleUpdate(float tpf) {
        /*
         * toggle the environment every 2 seconds
         */
        elapsedTime += tpf;
        if (elapsedTime > 2.0) {
            toggleEnvironment();
            elapsedTime = 0.0;
        }
    }

    void toggleEnvironment() {
        inSpace = !inSpace;
        updateMaterials();
    }

    void updateMaterials() {
        if (inSpace) {
            bicolor.setColor("BottomColor", ColorRGBA.White.clone());
            bicolor.setColor("TopColor", ColorRGBA.Black.clone());
        } else {
            bicolor.setColor("BottomColor", ColorRGBA.Brown.clone());
            bicolor.setColor("TopColor", ColorRGBA.Blue.clone());
        }
    }
}

I’m getting the dreaded
java.lang.ArrayIndexOutOfBoundsException: 1
at com.jme3.material.plugins.J3MLoader.readParam(J3MLoader.java:237)
at com.jme3.material.plugins.J3MLoader.readMaterialParams(J3MLoader.java:290)
at com.jme3.material.plugins.J3MLoader.loadFromRoot(J3MLoader.java:541)
at com.jme3.material.plugins.J3MLoader.load(J3MLoader.java:555)
at com.jme3.asset.DesktopAssetManager.loadAsset(DesktopAssetManager.java:288)
at com.jme3.material.Material.(Material.java:120)
at my.asteroid.states.HUDAppState.getTestSphere(HUDAppState.java:156)
at my.asteroid.states.HUDAppState.initialize(HUDAppState.java:92)
at com.jme3.app.state.AppStateManager.initializePending(AppStateManager.java:251)
at com.jme3.app.state.AppStateManager.update(AppStateManager.java:281)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:245)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:185)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:228)
at java.lang.Thread.run(Thread.java:744)

I took the space out from main() and the open bracket, per an old post.
I need a break. sick with a cold and am hungry… :wink:
And, thanks.

1 Like

I tested it with JME 3.1-stable and JME 3.2-6420. What version of jMonkeyEngine are you using?

Ah, 3.0 Stable. i’ll try upgrading…

1 Like

I did the upgrade. It works, sort of. I get the bottom color on top and bottom. (So, soled color) But the flashing to the two modes works. (Good to know)

I almost lost my whole project. I hacked into InputManager to add modifiers (shift/control) and to do it, I needed to copy a bunch of stuff and put it in JME-core.dir and us it as my main library. So when I upgraded, it was no longer valid. I had to recreate my whole project folder and copy the src and Assets over.

So, you see two colors… I’m trying different values for the 0.5 but, I don’t know why its not working. Oh, and, Dah, I should have tried real Java code in the Main(). That said, OpenGL and java are not the same.

If you’re seeing only the bottom color, perhaps your camera is looking at the bottom of the ball. Have you tried moving the camera and/or the ball?

Sry, that was it. in my hurry to not leave you hanging, I forgot to rotate it. That’s it. Perfect.
Thanks again.

1 Like

I’m glad to hear the issue is resolved.

Btw you may have noticed my fragment shader used texture coordinates (passed from the vertex shader using a varying) instead of position data, as you originally envisioned. This approach assumes a textured model. I’m sure it’s also possible to bypass texturing entirely and simply pass position data or color data to the fragment shader.

I got a picture of what it looks like and I should have a short movie showing it working soon.

2 Likes