When to inject glScissor settings?

How do I go about integrating glScissor() with JME to clip child nodes outside a specified region?  I've tried overriding the draw(Renderer r) method like this:

   @Override
   public void draw(Renderer r) {
      GL11.glEnable(GL11.GL_SCISSOR_TEST);
      GL11.glScissor(this.getWorldX(), this.getWorldY(), getWidth(), getHeight());
      super.draw(r);
      GL11.glDisable(GL11.GL_SCISSOR_TEST);
   }



However, if I do that, my glScissor region never gets applied.  If I comment out the glDisable() call, then the scissor state works (it clips everything drawn outside my rectangle) but what I'm trying to do is only clip the children of the node I'm currently drawing, not everything else.  Basically, I want to enable the scissor state while this Node's children are rendering, and then disable it for all other nodes.  What I think is happening is that the actual rendering isn't happening during the call to super.draw(r), but instead it's simply queued and actually rendered later (after my glDisable() call).  So I need an injection point during the actual render process...

Any ideas?  Am I explaining what I'm trying to do clearly?

Here's a couple of screenshots that might help illustrate...


Your code seems to work fine with a simple Quad.



From which class are you overriding draw() in your example?

I'm extending com.jme.scene.Node



Do you happen to have a quick and dirty example that works with a Quad?  Where it only clips for the Quad and not other items in the Scenegraph?



It might not help, since my "Chat Window" is a bunch of quads attached to nodes.  For example, each character in the text entry is a single quad.  My goal would be to snip the letters that extend beyond the edge of the window.  I could achieve a similar effect by manipulating the CullHints of the individual letters, but it would be easier if I could simply set up a rectangle and have any children of my Node that are outside the rectangle get clipped.



I also tried setting up a ClipState, but wasn't able to get it to work in the ORTHO render queue… I probably did something wrong :slight_smile:

Overriding the Node draw wont help much, you'd have to override the Text or Quad draw methods.



But i guess if your create a ScissorState and apply it to the Node, the chldren would inherit the state and you would get the desired effect.



To Test it simply, you could modify LWJGLStippleState and do a Scissor instead stipple





Here is a example using a simple quad (your version)



import org.lwjgl.opengl.GL11;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingSphere;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.Spatial.CullHint;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;

public class TestSimpleGame extends SimpleGame {
    public static void main(String[] args) {
        TestSimpleGame app = new TestSimpleGame();
        app.setConfigShowMode(ConfigShowMode.AlwaysShow);
        app.start();
    }

    protected void simpleInitGame() {
        display.setTitle("A Simple Test");
        Box box = new Box("my box", new Vector3f(0, 0, 0), 2, 2, 2);
        box.setModelBound(new BoundingSphere());
        box.updateModelBound();

        Quad q = new ScissorQuad(display.getWidth(), display.getHeight());
        q.setCullHint(CullHint.Never);
        q.setRenderQueueMode(Renderer.QUEUE_ORTHO);
        q.setLocalTranslation(display.getWidth() / 2, display.getHeight() / 2,
                0);
        graphNode.attachChild(q);
        rootNode.attachChild(box);

        KeyBindingManager.getKeyBindingManager().add("toggle",
                KeyInput.KEY_SPACE);
    }

    class ScissorQuad extends Quad {
        public ScissorQuad(float w, float h) {
            super("", w, h);
        }

        @Override
        public void draw(Renderer r) {
            if (KeyBindingManager.getKeyBindingManager().isValidCommand(
                    "toggle", true)) {
                // scissor disabled
                super.draw(r);
            } else {
                // scissor enabled
                GL11.glEnable(GL11.GL_SCISSOR_TEST);
                GL11.glScissor(50, 50, 90, 90);
                super.draw(r);
                GL11.glDisable(GL11.GL_SCISSOR_TEST);
            }
        }
    }
}

Modifying LWJGLStippleState to do a glScissor() mostly works to do what I want.  However, I have to apply it to each child Quad, I can't just apply it to the parent Node.  Even so, that's not too bad, and will probably work for what I'm trying to do.



I took a crack at creating a ScissorState, using StippleState as a guide, but I'm having a rough time of it.  I'm sure I'll figure out what bone-headed mistake I'm making once I've slept.



Any idea why adding the renderstate to the Node isn't propagating to the child Quads?

did you do a node.updateRenderstate() after applying the renderstate?

Yes indeed!



The relevant code: (UIComponentInstance extends Node, UIQuad extends Quad)


package mmo.client.ui.theme;

import java.util.LinkedList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.lwjgl.opengl.GL11;

import mmo.client.ui.UIColor;
import mmo.client.ui.UIQuad;
import mmo.client.ui.font.ImageFont;

import com.jme.input.KeyInput;
import com.jme.input.KeyInputListener;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.state.StippleState;
import com.jme.system.DisplaySystem;
import com.jme.util.Timer;

public class UITextEntry extends UIComponentInstance  implements KeyInputListener {

   private static final long serialVersionUID = 1L;

   private static Log logger = LogFactory.getLog(UITextEntry.class);
   
   private static final int MAXLENGTH = 1024;
   private int cursorX;
   private int cursorY;
   private float lastBlink;
   private float blinkInterval = .5f;
   private boolean cursorOn = false;
   private Timer timer = Timer.getTimer();
   private UIQuad cursor;
   private Node textEntry = new Node("TextEntry");
   private ImageFont font = null;
   private ColorRGBA textColor = null;
   private LinkedList<Character> textBuffer = new LinkedList<Character>();
   
   public UITextEntry(UIComponentInstance parent, UIComponent template,
         String name, int x, int y, int width, int height) {
      super(parent, template, name, x, y, width, height);
      cursorX = 1;
      cursorY = 2;
      UIElement comp = (UIElement) UserInterface.get().getComponent("A_InputCursor");
      cursor = new UIQuad("cursor", comp);
      attachChild(cursor);
      cursor.setCullHint(CullHint.Always);
      cursor.positionLowerLeft(cursorX, cursorY);
      attachChild(textEntry);
      font = UserInterface.get().getFont("EntryFont");
      textColor = UIColor.getColor(this.getStyle("TextColor"));
      StippleState ss = DisplaySystem.getDisplaySystem().getRenderer().createStippleState();
      ss.setEnabled(true);
      textEntry.setRenderState(ss);
      textEntry.updateRenderState();
   }
   

   private void blinkCursor() {
      if (cursorOn) {
         cursor.setCullHint(CullHint.Always);
         cursorOn = false;
      } else {
         cursor.setCullHint(CullHint.Never);
         cursorOn = true;
      }
   }
   
   @Override
   public void updateWorldData(float tpf) {
      super.updateWorldData(tpf);
      if (hasFocus() && timer.getTimeInSeconds()-lastBlink > blinkInterval) {
         lastBlink = timer.getTimeInSeconds();
         blinkCursor();
      }
   }
   
   @Override
   public void onEnter(int X, int Y) {
      // TODO Auto-generated method stub

   }

   @Override
   public void onExit(int X, int Y) {
      // TODO Auto-generated method stub

   }

   @Override
   public void onMouseDown(int button, int x, int y) {
      if (button == 0 && !hasFocus()) {
         UserInterface.get().setFocusWidget(this);
         logger.debug("Text box position: X:"+getWorldX()+" Y:"+getWorldY()+" W:"+getWidth()+" H:"+getHeight());
      }
   }

   @Override
   public void onMouseMove(int deltaX, int deltaY, int newX, int newY) {
      // TODO Auto-generated method stub

   }

   @Override
   public void onMouseUp(int button, int x, int y) {
      // TODO Auto-generated method stub

   }

   @Override
   public void onKey(char c, int keyCode, boolean pressed) {
      if (pressed) {
         if (keyCode == KeyInput.KEY_BACK && textBuffer.size() > 0) {
            char todelete = textBuffer.removeLast();
            if (textEntry.getQuantity() > 0 && todelete != ' ')
               textEntry.detachChildAt(textEntry.getQuantity()-1);
            cursorX -= font.getWidth(todelete);
            cursor.positionLowerLeft(cursorX, cursorY);
         } else if (font.isCharacterMapped(c) && textBuffer.size() < MAXLENGTH) {
            textBuffer.add(c);
            if (c != ' ') {
               UIQuad character = font.getRenderer().renderChar(c, textColor);
               /*StippleState ss = DisplaySystem.getDisplaySystem().getRenderer().createStippleState();
               ss.setEnabled(true);
               character.setRenderState(ss);
               character.updateRenderState(); */
               character.positionLowerLeft(cursorX, 0);
               textEntry.attachChild(character);
            }
            cursorX += font.getWidth(c);
            cursor.positionLowerLeft(cursorX, cursorY);
         }
      }
   }

}



And the hacked LWJGLStippleState:

package com.jme.scene.state.lwjgl;

import java.nio.ByteBuffer;

import org.lwjgl.opengl.GL11;

import com.jme.renderer.RenderContext;
import com.jme.scene.state.StateRecord;
import com.jme.scene.state.StippleState;
import com.jme.scene.state.lwjgl.records.StippleStateRecord;
import com.jme.system.DisplaySystem;

/**
 * LWJGL implementation of {@link StippleState}
 * @author Christoph Luder
 */
public class LWJGLStippleState extends StippleState {
    private static final long serialVersionUID = 1L;
    /**
     * <code>apply</code>
     * @see com.jme.scene.state.StippleState#apply()
     */
    @Override
    public void apply() {
        // ask for the current state record
        RenderContext<?> context = DisplaySystem.getDisplaySystem().getCurrentContext();
        StippleStateRecord record = (StippleStateRecord) context.getStateRecord(StateType.Stipple);
        context.currentStates[StateType.Stipple.ordinal()] = this;

        if (isEnabled()) {
            GL11.glEnable(GL11.GL_SCISSOR_TEST);
            //ByteBuffer mask = getStippleMask();
            GL11.glScissor(101, 6, 408, 20);
        } else {
            GL11.glDisable(GL11.GL_SCISSOR_TEST);
        }

        if (!record.isValid())
            record.validate();
    }

    /**
     * creates a new StippleStateRecord
     */
    @Override
    public StateRecord createStateRecord() {
        return new StippleStateRecord();
    }
}



I've verified the scissor coordinates are correct.  The code above does no clipping.  But, if I uncomment the block where I attach the StippleState to each character (a Quad), then it works perfectly.

Screenshot of the StippleState applied to the Node:


Screenshot of the StippleState applied to the Node and the child Quads:


One thing to note, is that the scissor state doesn't clip my cursor, which is expected since the cursor quad is attached directly to the grandparent Node which doesn't have a StippleState applied.  However, what is unusual is that the cursor quad is leaving ghost images.  I've not seen that happen before.

i'm getting strange artefacts also with the ScissorState enabled, i am not sure why it happens tho.

Ok, I figured out the child propagation issue.  What I didn't realise is that one must call updateRenderState() on the parent if any children are added.  I assumed that you could apply a RenderState to a node, and then from that point onward, all children would inherit that state.  In fact, only children currently attached inherit a state, and if you add children later, you have to call updateRenderState() again to have the RenderStates propagate to the new children.



Even with that fix, however, I'm still getting wierd artifacts whenever my cursor quad leaves the glScissor region, even though it isn't a child of the textentry Node, nor does it have the glScissor applied to it.



I'm not too worried about that, since I'll be constraining the cursor to the visible region anyway, and simply scrolling the text quads as appropriate.



My next step is to create a true ScissorState instead of using a hacked up StippleState.  If I can get it working and provide a diff, is that something that could be added to JME?  I can't really do it outside of JME as it requires modifications to the core Render class (adding ScissorState to the enum, etc).

I made the ScissorState already, but since i still got the artefact, i didnt post the diff.

I'll post it tonight so you can play around with it easier.

If you can get it working smoothely, i'm sure it can be added to jme.



While looking into the problem, i also saw workarounds using stencil buffer or viewport changes, maybe one of those methods works better then scissor.

I'll look into those options as well and see if they'll work for what I'm trying to do.  A ScissorState diff would be very appreciated in the interim.  Thank you!

I was able to achieve the desired effect using clipstates.  They are a little more cumbersome to work with than glScissor, but so far I haven't had any problems with artifacts or other issues.

The patch is here -> Click, but as mentioned it dosent work correctly.

I’m glad you got it working with clipstate

I am also having trouble getting ClipState to work in the Ortho queue, any info on how you got this working?