Hide parts of a model

Hello to all.

I started to use jMe a few month ago in order to achieve some artistic projects. I’m working in the field of arts & technologies research, even if I have no specific background in computer programming. Try to learn it by myself, so I lack a lot of knowledge.



Until now, the jMe forum has been a real great help. It solved almost all my problems. But I’d like to have your opinion this time.



I’m working on a quite simple project now. I want to show a landscape, made of primitives shapes like long boxes constructing a 3D grid , which looks complete from just one specific point of view. If the user (spectator) moves he’s viewpoint (the camera) around the landscape, he would see that all the part placed behind an object doesn’t exists. To make it simple, imagine that you have two (5,5,5) sized boxes. One is placed in the world at (0,-10,10), the second one at (0,10,-10). Depending of the cam location, looking at the boxes will show you a complete one, and the other behind will be partially shown (traditional perspective rule). Then, from this very same point of view, if the hidden part of the boxes located behind doesn’t exists at all, you’ll see the same thing. But i you move, you can discover that the landscape is just partial, all object are made to be seen from one unique viewpoint.



The idea is then to cut off every thing that is outside of the 6 camera planes, and to cut all the models depending of the perspective main point (don’t know the name in english).



What I thought was to create my 3D grid in jMe, to look at it from a nice position, to generate a model for the camera’s planes, to attach a point light to my camera and to use shadows volume to get all the volume i should use to cut the landscape. Then I export all of this to an .obj file, and a 3D software should allow me to make my holes with boolean operations.

Once everything is cut, I just import the new model in jMe, place the cam at the good location, and continue with the interfacing for exhibition etc.



I wrote a very simple and basic JmeToObj class to export jMe scene objects to an obj file. It’s not wonderful, but it works.



Here is a view of my 2 obj files:

1 - The models of the scene and the cam planes





2- Shadows volumes





I thought I could use Booleans in 3D apps, but I tried many and I have always the same result : crash of the app after some time. Cutting the objects with the camera planes is ok, but for the shadows, it’s not the same story… Models get dirty quickly, and makes the boolean op buggy.



So, I’m quite lost now. I was wondering if I could use the ClipState for this, but it seems impossible after some tests. I could use it to exclude all part of models that are not inside shadows, but I want the inverse…



I read a post about the possibility of a ClipRegion, which could be something useful for me : using the shadow volumes to define a piece of the model that should not be rendered. It seems that we need to use the StencilState to achieve that, but it’s far away from my understanding, regarding to what I read on the net at least. I had a look to the ShadowRenderPass code, but it’s too hard for me :cry:



So, what do you think about it ? Does anyone of you have an idea of how to do this in jMe, or with something else ?



Any comment/help would be appreciate.



Thanks in advance, and thanks to the jMe team for this great engine that saved me and for this forum, of course!



Dom

I’m still trying to achieve this hide model part stuff.



I’ve search for a while on the net, and it seems that the stencil technic is the best way to do it. I found some piece of code and made my best to understand something to all of this.



I’m still fighting with the stencil usage in jMe, and finally could make something, but it’s not finished yet.



For testing, i’m using 2 box like this :







Looking at the shadowedRenderPass code and using some parts of it, here’s what I could make :







And :







So, now I’d like to render both of this together, but I can’t figure out how to do it!?

Can anyone give me some hints about this ?



here’s the code part of the 2 different subtractions :


   protected void subFrontRender(Renderer r){

      colorMask.setEnabled(true);
      colorMask.setAll(false);
      context.enforceState(colorMask);

      renderScene(r);
      saveEnforcedStates();

      cs.setEnabled(true);
      cs.setCullMode(CullState.CS_BACK);
      context.enforceState(cs);

      zBuffer.setEnabled(true);
      zBuffer.setWritable(false);
      zBuffer.setFunction(ZBufferState.CF_LESS);
      context.enforceState(zBuffer);

      stencil.setEnabled(true);
      stencil.setStencilFunc(StencilState.SF_ALWAYS);
      stencil.setStencilRef(0);
      stencil.setStencilMask(~0);
      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_INCR);

      context.enforceState(stencil);

      renderOccluders(r);

      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_DECR);
      context.enforceState(stencil);

      cs.setCullMode(CullState.CS_FRONT);
      context.enforceState(cs);

      renderOccluders(r);

      zBuffer.setEnabled(true);
      zBuffer.setWritable(true);
      zBuffer.setFunction(ZBufferState.CF_LEQUAL);
      context.enforceState(zBuffer);

      cs.setCullMode(CullState.CS_NONE);
      context.enforceState(cs);

      colorMask.setAll(true);
      context.enforceState(colorMask);

      stencil.setStencilFunc(StencilState.SF_EQUAL);
      stencil.setStencilRef(0);
      stencil.setStencilMask(~0);
      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_KEEP);
      context.enforceState(stencil);

      renderScene(r);

      saveEnforcedStates();
      //replaceEnforcedStates();

      stencil.setEnabled(false);
      r.clearStencilBuffer();
   }



And for the second image :

protected void subOccluderRender(Renderer r){

      colorMask.setEnabled(true);
      colorMask.setAll(false);
      context.enforceState(colorMask);

      cs.setEnabled(true);
      cs.setCullMode(CullState.CS_FRONT);
      context.enforceState(cs);

      renderOccluders(r);
      saveEnforcedStates();


      cs.setEnabled(true);
      cs.setCullMode(CullState.CS_BACK);
      context.enforceState(cs);

      zBuffer.setEnabled(true);
      zBuffer.setWritable(false);
      zBuffer.setFunction(ZBufferState.CF_LESS);
      context.enforceState(zBuffer);

      stencil.setEnabled(true);
      stencil.setStencilFunc(StencilState.SF_ALWAYS);
      stencil.setStencilRef(0);
      stencil.setStencilMask(1);
      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_INCR);

      context.enforceState(stencil);

      renderScene(r);

      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_DECR);
      context.enforceState(stencil);

      cs.setCullMode(CullState.CS_FRONT);
      context.enforceState(cs);

      renderScene(r);

      zBuffer.setEnabled(true);
      zBuffer.setWritable(true);
      zBuffer.setFunction(ZBufferState.CF_ALWAYS);
      context.enforceState(zBuffer);

      colorMask.setAll(true);
      context.enforceState(colorMask);

      cs.setCullMode(CullState.CS_FRONT);
      context.enforceState(cs);

      stencil.setStencilFunc(StencilState.SF_NOTEQUAL);
      stencil.setStencilRef(0);
      stencil.setStencilMask(1);
      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_KEEP);
      context.enforceState(stencil);

      renderOccluders(r);

      saveEnforcedStates();
      //replaceEnforcedStates();

      stencil.setEnabled(false);
      r.clearStencilBuffer();
   }



How should I mix those to parts ? I tried many things, but without success. I'm still lacking of experience in this.

Thanks for your help.

Dom

Why not use as separate pass now for each of the parts you want to render.

I was thinking about doing this, but I don't see how to make it.



Should I instanciate the same RenderPass twice, tell the first one to render one part and the second one to render the other part ?



something like (where occluders are the volume I want to use to cut other objects):



private static ExtractPassBase sPass = new ExtractPassBase();

private static ExtractPassBase sPass2 = new ExtractPassBase();







sPass.occludersRender = false;

sPass.add(box);

sPass.addOccluder(floor);

       

pManager.add(sPass);

       

sPass2.occludersRender = true;

sPass2.add(box);

sPass2.addOccluder(floor);

       

pManager.add(sPass2);



I tried to do this, but the second pass erase the first, and I have the same result then.



:?


So, still working on it.



By using 2 "pass" with care, I could mix my 2 object's parts.

I could achieve nice results with one object from which several others objects are substracted, but I have troubles when more than one object are used as base for the substraction.



It's seems that I have real zBuffer issues. As I have to clearBuffers() 2 times in my process (once in each pass), I have strange things in the final result. I guess I have to record the zBuffer at the end of the first pass, and to restore it before I draw my final parts in the 2nd pass.



Anyone know how to do it ? Had a look to the "showDepth" function that we can use in SimpleGame (with the F3 key, very very useful), I think I could save the zBuffer in a IntBuffer, and also as a texture but I can't restore it after in the current zBuffer to mix it with the final part of the result.



Any clues on saving a particular zBuffer and restore it ?



Thanks


hmm do you mean saveEnforcedStates() / replaceEnforcedStates()?.

in your first post you have replace call commented out.

I wrote my Pass one more time. This time I could use the stencil better than before. Getting used with it maybe.

So, I have good results with two objects. With more, I still have issues, and I will have to use the stencil buffer in a different way. I’ll have to use different values in the stencil for each objects. Does anyone knows what is the max value that we can use for stencil bits ? (16?)



Anyway, the stencil has a limit, and I know I will encounter my zBuffer problem again when I’ll try to use more objects than the stencil can handle. I’m not sure if it is possible to use FBO as a source for the current zBuffer, so that I can store the values in a buffer object and write it to the context zBuff… It’s the only way I see to achieve what I’m looking for. If anyone has any advice about it, that would be great.



I remeber I read a post in this forum which was entitled “Is it possible to fill a model with air”, or something similar. One screenshot to show a box filled with “air” by a moving sphere.






Nice. :slight_smile:

Yeah, I agree with renanse's sentiments:  Nice :slight_smile:



I believe the stencil bit limit is card (and driver) dependent, but I could be wrong.

Thanks,



but it's not over yet I'll keep working on it.



For the stencil bits, it looks like you're right, basixs.

On my MacBookPro, the OpenGL renderer Info of my NVidia card says "yes" to 8 bits per pixels in the stencil buffer modes section, and "no" to others modes…

Guess it's my answer.

hey Dom.



Is it possible to get my hands on the code for the pass and the testcase with the box and the sphere?



I'd really appreciate it  :slight_smile:



so long,

Andy

Hi.



Well, it's not impossible  ;). What would you like to do with it ? (just curious)

I had to stop working on it for a some time, so I have no further progress. It's usable only for 2 objects : one that will "dig" in another one. I may will work again on it in a few time, in anyway I will need it. But there's still a lot to do for it. So don't expect too much from it in it's actual state.



If I post the code here, in the forum (the renderPass and the small testcase), is it ok for you ? (I'll try to clean it a little)



Oh! Also, I'm a real newbie, so my code is maybe not very "clean" nor effective. So don't be surprised!



Dom

Would this be a good solution to creating a cave entrance in Terrain …



Reminds me also of a fluke that happened, I creating an open doorway in a wall that was made of just one quad.

@theprism : Maybe not. Closed volumes are needed for this renderPass to work. As a terrain is like a plane with lot vertices of different heights, it can't be used. If the terrain is a kind of box with the top plane being the terrain, then maybe it can be used. But I'm not sure it's a good way to do an entrance in a Terrain anyway…



@dhdd : I'll try to clean my code tomorrow (France time). I had a look at it, and it's quite a mess. I was still searching a solution to make hole from several model to several other one when I had to switch to another project. So, it's full of garbage now.

I'll post it here then. Hope it's ok with you!



I had discussions with people much more good than me in 3D programming ans maths that seems to have some ideas and workaround to acheive those boolean operation I'm looking for. There is some hope I can make them use jMe for it, which would be nice I think. Nothing is done, but we'll see.

well, what i am trying to do is this:



I have a bunch of Quads in my scene (all in one plane) and i want to make some quads invisible and some quads partially invisible. This means that i want to have a mask in said two-dimensional plane, much in the sens of an alpha channel in Photoshop. There is no need for Geometry manipulation i guess.



Can i achieve something like that? How?



Anyway: Lateron in my project, I need something exactly like your testcase anyway  :wink:



so long and thanks.

You won't be able to do it with my ExctractPass. It's made for closed objects because of culling issues. I have to render several times the front and back faces of shapes to construct 2D masks on differents layers of the Stencil Buffer. The result is what you see in the test case with the box and the sphere.



Technically, what you want to do with your Quads is much more simple than that. While I was experimenting for my ExctractPass, I made some stuff similar to what you want to do.

You have to use the stencil buffer for it. It's not so difficult I think. One or several shapes have to be rendered in the stencil buffer to fill the area you want to mask, then you render the Quads only where the stencil is empty. Other objects should be rendrer after, in an other RenderPass, to avoid strange disappearence of shapes that should be behind your mask. This is due to zBuffer issues. Does that make sense to you?

You should use non flat object as base for filling the stencil and create your mask. If your Cam is in front of the Quads, your masking object needs to be in front of the Quads also, and it's the same if you're behind the Quad. So, I would suggest to you to use some kind of thin box (or whatever shape you want, cylinder ect) and to place it where the Quads plane is, so that Quads are like "inside" the mask object. It's the trick to acheive this.

Well, at least, this is what I would do if I were you. As I'm not a specialist (not at all), so there's maybe a more effective way to do it.



I'll post the code of both the Pass en TestCase now. Hope it will help. If you would like to, we could try your Quad stuff together. I'll maybe have usage for it too someday.

So, here we are.



ExctractPass code :



import java.util.ArrayList;
import com.jme.renderer.Renderer;
import com.jme.scene.Spatial;
import com.jme.scene.state.ColorMaskState;
import com.jme.scene.state.CullState;
import com.jme.scene.state.StencilState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.renderer.pass.*;

/**
 * RenderPass that will produce a kind of 3D boolean operation "substraction" on shapes.
 * Models should be added to the Pass by yourPass.add(Spatial) and yourPass.addOccluders(Spatial).
 * Should be used only for 2 objects now. Other objects should be passed to another RendererPass.
 * @author Dominique Cunin, using code made by the jMe dev team
 */
public class ExtractPass extends Pass{

   private static final long serialVersionUID = 1L;

   /** whether or not the renderstates for this pass have been init'd yet. */
   protected boolean initialised = false;

   /** list of occluders registered with this pass. Occluders are geometries that will be used to make a hole in other geometries */
   protected ArrayList<Spatial> occluders = new ArrayList<Spatial>();

   /**
    * <code>addOccluder</code> adds an occluder to this pass.
    *
    * @param toAdd
    *            Occluder Spatial to add to this pass.
    *            Occluders are geometries that will be used to make a hole in other geometries
    */
   public void addOccluder(Spatial toAdd) {
      occluders.add(toAdd);
   }

   /**
    * Render geometries in which we want to make a hole
    * @param r the Renderer to be used
    */
   protected void renderScene(Renderer r) {
      for (int i = 0, sSize = spatials.size(); i < sSize; i++) {
         Spatial s = spatials.get(i);
         s.onDraw(r);
      }
      r.renderQueue();
   }

   /**
    * Render geometries that will make holes in others shapes
    * @param r the Renderer to be used
    */
   protected void renderOccluders(Renderer r) {
      for (int i = 0, sSize = occluders.size(); i < sSize; i++) {
         Spatial s = occluders.get(i);
         s.onDraw(r);
      }
      r.renderQueue();
   }

   /**
    * In the case we want to render just one occluder
    * @param s Spatial
    * @param r Renderer
    */
   protected void renderOneOccluders(Spatial s, Renderer r) {
      s.onDraw(r);
   }

   ColorMaskState colorMask;
   ZBufferState zBuffer;
   CullState cs;
   StencilState stencil;

   /**
    * Init all buffers that will need to be used during the pass
    */
   protected void init() {
      if (initialised){
         return;
      }
      initialised = true;

      Renderer r = DisplaySystem.getDisplaySystem().getRenderer();

      colorMask = r.createColorMaskState();
      zBuffer = r.createZBufferState();
      cs = r.createCullState();
      stencil = r.createStencilState();
   }

   /**
    * Just a way to distinguish the 2 parts of the Pass. This one is for the objects in which a hole will be made
    * @param r Renderer
    */
   protected void targetPass(Renderer r){

      colorMask.setEnabled(false);
      colorMask.setAll(false);
      context.enforceState(colorMask);

      renderScene(r);

      cs.setEnabled(true);
      cs.setCullMode(CullState.CS_BACK);
      context.enforceState(cs);

      zBuffer.setEnabled(true);
      zBuffer.setWritable(false);
      zBuffer.setFunction(ZBufferState.CF_LESS);
      context.enforceState(zBuffer);

      stencil.setEnabled(true);
      stencil.setStencilFunc(StencilState.SF_ALWAYS);
      stencil.setStencilRef(0);
      stencil.setStencilMask(~0);
      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_INCR);

      context.enforceState(stencil);

      renderOccluders(r);

      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_DECR);
      context.enforceState(stencil);

      cs.setCullMode(CullState.CS_FRONT);
      context.enforceState(cs);

      renderOccluders(r);

      r.clearBuffers();

      stencil.setEnabled(false);
      context.enforceState(stencil);
   }

   /**
    * Just a way to distinguish the 2 parts of the Pass. This one is for the objects that will make holes.
    * @param r Renderer
    */
   protected void occludersPass(Renderer r){

      cs.setCullMode(CullState.CS_FRONT);
      context.enforceState(cs);

      zBuffer.setWritable(true);
      context.enforceState(zBuffer);

      renderOccluders(r);

      cs.setCullMode(CullState.CS_BACK);
      context.enforceState(cs);

      zBuffer.setWritable(false);
      zBuffer.setEnabled(true);
      zBuffer.setFunction(ZBufferState.CF_LEQUAL);
      context.enforceState(zBuffer);

      stencil.setEnabled(true);
      stencil.setStencilFunc(StencilState.SF_EQUAL);
      stencil.setStencilRef(1);
      stencil.setStencilMask(~0);
      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_INCR);
      context.enforceState(stencil);

      renderScene(r);

      cs.setCullMode(CullState.CS_FRONT);
      context.enforceState(cs);
      
      stencil.setStencilFunc(StencilState.SF_EQUAL);
      stencil.setStencilRef(2);
      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_DECR);
      context.enforceState(stencil);

      renderScene(r);

      r.clearBuffers();

      zBuffer.setEnabled(true);
      zBuffer.setWritable(true);
      zBuffer.setFunction(ZBufferState.CF_LEQUAL);
      context.enforceState(zBuffer);

      colorMask.setAll(true);
      context.enforceState(colorMask);

      cs.setCullMode(CullState.CS_FRONT);
      context.enforceState(cs);

      stencil.setStencilFunc(StencilState.SF_EQUAL);
      stencil.setStencilRef(2);
      stencil.setStencilMask(~0);
      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_KEEP);
      context.enforceState(stencil);

      renderOccluders(r);
   
      cs.setCullMode(CullState.CS_NONE);
      context.enforceState(cs);

      stencil.setEnabled(true);
      stencil.setStencilFunc(StencilState.SF_EQUAL);
      stencil.setStencilRef(0);
      stencil.setStencilMask(~0);
      stencil.setStencilOpFail(StencilState.SO_KEEP);
      stencil.setStencilOpZFail(StencilState.SO_KEEP);
      stencil.setStencilOpZPass(StencilState.SO_KEEP);
      context.enforceState(stencil);

      renderScene(r);

      stencil.setEnabled(false);
      context.enforceState(stencil);
      r.clearStencilBuffer();
   }

   /**
    * Render the scene using all the passes needed.
    */
   public void doRender(Renderer r) {
      init();
      targetPass(r);
      occludersPass(r);
   }

}



And the small TestCase


import com.jme.animation.SpatialTransformer;
import com.jme.app.AbstractGame;
import com.jme.app.SimplePassGame;
import com.jme.bounding.BoundingBox;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.pass.RenderPass;
import paysage.ExtractPass;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Sphere;

public class TestCase extends SimplePassGame{

   public static void main(String[] args){
      TestCase app = new TestCase();
      //need a minimum of stencil bits
      app.stencilBits = 1;
      app.setDialogBehaviour(AbstractGame.ALWAYS_SHOW_PROPS_DIALOG);
      app.start();
   }

   private static ExtractPass exPass = new ExtractPass();

   protected void simpleInitGame(){

      display.getRenderer().setBackgroundColor(new ColorRGBA(0,0,0,0));

      //the box
      Box box = new Box("box", new Vector3f(0,0,0), 10,30,5);
      box.setModelBound(new BoundingBox());
      box.updateModelBound();
      rootNode.attachChild(box);
      //add it to the ExtractPass
      exPass.add(box);

      //the sphere that will make hole in the box
      Sphere occ = new Sphere("socc2", new Vector3f(0,0,0), 40,40,15);
      occ.setModelBound(new BoundingBox());
      occ.updateModelBound();
      rootNode.attachChild(occ);

      //let's move it a little
      SpatialTransformer transformer=new SpatialTransformer(1);
      transformer.setObject(occ, 0, -1);
      transformer.setPosition(0, 0, new Vector3f(0,0,0));
      transformer.setPosition(0, 5, new Vector3f(20,-30,0));
      transformer.setPosition(0, 10, new Vector3f(0,0,20));
      transformer.setPosition(0, 15, new Vector3f(-20,30,0));
      transformer.setRepeatType(SpatialTransformer.RT_CYCLE);
      occ.addController(transformer);
      exPass.addOccluder(occ);
      
      //add the pass
      pManager.add(exPass);

      //create a new pass to render other objects that don't need to be processed, if any
      RenderPass rPass = new RenderPass();
      rPass.add(fpsNode);
      pManager.add(rPass);

   }

}



hey there,



thank you Dom, I will have to make extensive use of your code later in the project … and it actually did make some sense what you explained  :wink:



For my little Quad thingy => BEHOLD THE CLIPSTATE! Its exactly what I need for the current problem.



Thank you so far Dom, I'll let you know when I'm all over your code  :wink:



so long,

Andy

May I suggest that the above code by Dom is modified for JME-2.0 (I have done this) and put into a jmetest package as I spent quite a while trying to find an example of using the stencil buffer and what is possible with it.

(Thanks for posting it Dom)