StandardGame + Bloom

For some time I tried to add Bloom to StandardGame.



I read the topics, but I didn't work(There are parts of solutions, but I don't know what to do). NullPointerException Error…

Is there somewhere a complete example with Bloom and StandardGame?



My Standardgame is combined with Physic (–> Physic Fun).



My Code:




package game;


import game.bin.GameApp;
import game.bin.gamesys.EngineGameState;
import game.bin.gamesys.PhysicsGame;
import game.bin.gamesys.ResourceLocatorAdvanced;
import game.bin.gamesys.ResourceLocatorLibaryTool;
import game.bin.gamesys.RuntimeCash;

import java.net.URISyntaxException;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jmex.game.state.GameStateManager;

/**
 * The Entry point.
 * Creates a game instance, points the resource locator to the resources
 * and creates / activated the GameStates.
 * 
 * @author Christoph Luder
 */
public class Main {
   
   private static PhysicsGame game;
   private static EngineGameState gamestate;
   
   
   public static void main(String[] args) throws InterruptedException, ExecutionException {
      // only show Warnings
       System.setProperty("jme.stats", "set");
      Logger.getLogger("com.jme").setLevel(Level.WARNING);
      Logger.getLogger("com.jmex").setLevel(Level.WARNING);

            
      // set up resource search paths
      try {
         ResourceLocatorLibaryTool.addResourceLocator(ResourceLocatorLibaryTool.TYPE_AUDIO,
               new ResourceLocatorAdvanced(Main.class.getClassLoader().getResource("game/data/audio/")));
         ResourceLocatorLibaryTool.addResourceLocator(ResourceLocatorLibaryTool.TYPE_SOUND,
               new ResourceLocatorAdvanced(Main.class.getClassLoader().getResource("game/data/sound/")));
         ResourceLocatorLibaryTool.addResourceLocator(ResourceLocatorLibaryTool.TYPE_TEXTURE,
               new ResourceLocatorAdvanced(Main.class.getClassLoader().getResource("game/data/texture/")));
         ResourceLocatorLibaryTool.addResourceLocator(ResourceLocatorLibaryTool.TYPE_MODEL,
               new ResourceLocatorAdvanced(Main.class.getClassLoader().getResource("game/data/model/")));
         ResourceLocatorLibaryTool.addResourceLocator(ResourceLocatorLibaryTool.TYPE_SHADER,
               new ResourceLocatorAdvanced(Main.class.getClassLoader().getResource("game/data/shader/")));
         ResourceLocatorLibaryTool.addResourceLocator(ResourceLocatorLibaryTool.TYPE_PARTICLE,
               new ResourceLocatorAdvanced(Main.class.getClassLoader().getResource("game/data/particle/")));
      } catch (URISyntaxException e1) {
         PhysicsGame.get().getGame().finish();
      }
      
      // create a StandardGame instance, set some settings and start it up
      game = PhysicsGame.get();
      game.getGame().getSettings().setFullscreen(false);
      game.getGame().getSettings().setWidth(800);
      game.getGame().getSettings().setHeight(600);
      game.getGame().getSettings().setFramerate(60);
      game.getGame().getSettings().setVerticalSync(true);
      game.getGame().getSettings().setStencilBits(4);
      game.getGame().getSettings().setDepthBits(24);
      game.getGame().getSettings().setSamples(4);
      game.getGame().getSettings().setSFX(true);
      game.getGame().getSettings().setMusic(false);
      game.getGame().start();
      

      // create and activate the GameStates
      
      gamestate = new EngineGameState("Engine");
      GameStateManager.getInstance().attachChild(gamestate);
      gamestate.setActive(true);
      
      RuntimeCash.setPhysicsSpace(gamestate.getPhysicsSpace());
      
      gamestate.getRootNode().attachChild(new GameApp("GameApp"));
      gamestate.getRootNode().updateGeometricState(0, true);
        gamestate.getRootNode().updateRenderState();
      
      GameStateManager.getInstance().activateAllChildren();
   }
}




package game.bin.gamesys;

import java.util.logging.Logger;

import com.jme.input.FirstPersonHandler;
import com.jme.input.InputHandler;
import com.jme.input.KeyInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.scene.Node;
import com.jme.scene.state.GLSLShaderObjectsState;
import com.jme.system.DisplaySystem;
import com.jme.util.geom.Debugger;
import com.jmedemos.physics_fun.physics.PhysicsWindCallback;
import com.jmedemos.physics_fun.util.SceneSettings;
import com.jmex.physics.PhysicsDebugger;
import com.jmex.physics.StaticPhysicsNode;
import com.jmex.physics.util.PhysicsPicker;
import com.jmex.physics.util.states.PhysicsGameState;

/**
 * The main GameState.
 * Creates the Physics Playground.
 *
 * @author Christoph Luder
 */
public class EngineGameState extends PhysicsGameState {
    /** reference to the camera */
   private Camera cam = DisplaySystem.getDisplaySystem().getRenderer().getCamera();
   /** we want to move the camera first person style */
   private FirstPersonHandler movementInput = null;
   /** InputHandler for the physics picker and basic command */
   private InputHandler input = new InputHandler();
   /** the static floor */
   private StaticPhysicsNode floor = null;
   /** The Node where newly created objects are attached to. */
   private Node objectsNode = null;
   
   /** should the physics debug view be rendereed */
   private boolean showPhysics = false;
   /** should the bounding boxes be rendered */
   private boolean showBounds = false;

   /** the physics picker */
   private PhysicsPicker picker = null;
   /** The physics Wind. */
   private PhysicsWindCallback wind = null;
   
   /** Display */
   private DisplaySystem display = DisplaySystem.getDisplaySystem();
   /** GLSLShader */
   private GLSLShaderObjectsState so_normalmap;
   /** WaterShader */
   
   
   /** Logger */
   private static final Logger logger = Logger.getLogger(EngineGameState.class.getName());
   
   /**
    * Constructs the MainGameState.
    * Creates the scene and add the different objects to the Scenegraph.
    *
    * @param name name of the GameState
    */
   
   
   public EngineGameState(String name) {
      super(name);
      
      // create the scene
        picker = new PhysicsPicker( input, rootNode, getPhysicsSpace(), true);
        picker.getInputHandler().setEnabled(false);
       
        init();
        setupInput();
   }
   
   
   /**
    * Initializes the rootNodes RenderStates, initializes the Camera and
    */
   private void init() {
      
       // create a first person controller to move the Camera with W,A,S,D and mouse look
       movementInput = new FirstPersonHandler(cam, 15.0f, 0.5f);
       // move the camera a bit backwards and up
       cam.setLocation(new Vector3f(2, 10, 15));
      
       // create a Physics update callback, to simulate basic wind force
       wind = new PhysicsWindCallback(SceneSettings.get().getWindVariation(),
                                       SceneSettings.get().getWindForce());
       getPhysicsSpace().addToUpdateCallbacks(wind);
      
       if(RuntimeCash.getNormalMapping()){
          so_normalmap = display.getRenderer().createGLSLShaderObjectsState();

           // Check is GLSL is supported on current hardware.
           if (!GLSLShaderObjectsState.isSupported()) {
               logger.severe("Your graphics card does not support GLSL programs, and thus cannot run Normal Mapping.");
               RuntimeCash.setNormalMapping(false);
           }
          
          reloadShader();
       }
      
   }
   
   
   public void reloadShader() {
      
      /*GLSLShaderObjectsState testShader = DisplaySystem.getDisplaySystem()
                .getRenderer().createGLSLShaderObjectsState();
        try {
           testShader.load(
                 ResourceLocatorLibaryTool.locateResource(ResourceLocatorLibaryTool.TYPE_SHADER, "normalmap.vert"),
                 ResourceLocatorLibaryTool.locateResource(ResourceLocatorLibaryTool.TYPE_SHADER, "normalmap.frag")
                 );
           
            testShader.apply();
            DisplaySystem.getDisplaySystem().getRenderer().checkCardError();
        } catch (JmeException e) {
           RuntimeCash.setNormalMapping(false);
            logger.log(Level.WARNING, "Failed to reload shader", e);
            return;
        }*/
       
        so_normalmap.load(
             ResourceLocatorLibaryTool.locateResource(ResourceLocatorLibaryTool.TYPE_SHADER, "normalmap.vert"),
             ResourceLocatorLibaryTool.locateResource(ResourceLocatorLibaryTool.TYPE_SHADER, "normalmap.frag")
             );
        so_normalmap.setUniform("baseMap", 0);
        so_normalmap.setUniform("normalMap", 1);
        so_normalmap.setUniform("specularMap", 2);
       
        logger.info("Shader reloaded...");
       
        RuntimeCash.setGLSLShaderObjectNormalMap(so_normalmap);
    }
   
   /**
    * set up some key actions.
    * - SPACE to release a new object,
    * - ESC to quit the game
    * - TAB to enable / disable the GUI GameState
    */
   private void setupInput() {
      input.addAction( new InputAction() {
         public void performAction( InputActionEvent evt ) {
            if ( evt.getTriggerPressed() ) {
               PhysicsGame.get().getGame().finish();
               System.exit(0);
            }
         }
      }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_ESCAPE, InputHandler.AXIS_NONE, false );
      
      input.addAction( new InputAction() {
          public void performAction( InputActionEvent evt ) {
              if ( evt.getTriggerPressed() ) {
                 System.out.println("Spawn Object");
              }
          }
      }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_SPACE, InputHandler.AXIS_NONE, false );
      
      input.addAction( new InputAction() {
          public void performAction( InputActionEvent evt ) {
              if ( evt.getTriggerPressed() ) {
                 System.out.println("Get Tab");
              }
          }
      }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_TAB, InputHandler.AXIS_NONE, false );
   }

   /**
    * we update the input controllers and some physic object if needed,
    * then we update the physics world and call updateGeometricstate()
    * which happens in super.update().
    */
   @Override
   public void update(float tpf) {
      input.update(tpf);
//      swing.update();
      
      /*if(RuntimeCash.getNormalMapping()){
          reloadShader();
       }*/
      
      if (movementInput.isEnabled()) {
         movementInput.update(tpf);
      }
      
      rootNode.updateGeometricState(tpf, true);
      super.update(tpf);
      
   }

   /**
    * render the scene, draw bounds or physics if needed.
    */
   @Override
   public void render(float tpf) {
       super.render(tpf);
       if (showPhysics) {
           PhysicsDebugger.drawPhysics(getPhysicsSpace(),
                   DisplaySystem.getDisplaySystem().getRenderer());
       }
      
       if (showBounds) {
           Debugger.drawBounds(getRootNode(),
                   DisplaySystem.getDisplaySystem().getRenderer());
       }
   }

   public boolean isShowPhysics() {
      return showPhysics;
   }

   public void setShowPhysics(boolean showPhysics) {
      this.showPhysics = showPhysics;
   }

    public PhysicsPicker getPicker() {
        return picker;
    }

    public PhysicsWindCallback getWind() {
        return wind;
    }

    public void setShowBounds(boolean showBounds) {
        this.showBounds = showBounds;
    }
   
    public void quit() {
       if (display != null){
            display.close();
       }
        System.exit( 0 );
    }
}



package game.bin.gamesys;

import com.jmex.game.StandardGame;

/**
 * Singleton wrapper for StandardGame.
 * This is needed to be able shutdown the Game when something unexpected happens.
 * @author Christoph Luder
 */
public class PhysicsGame {
   private StandardGame game = null;
   private static PhysicsGame instance = null;

   private PhysicsGame() {
      game = new StandardGame("Z.E.R.O - Zombie Emergency Rescue Operations");
   }
   /**
    * @return the PhysicsGame instance.
    */
   public static PhysicsGame get() {
      if (instance == null) {
         instance = new PhysicsGame();
      }
      return instance;
   }
   
   /**
    * @return the standardGame instance.
    */
   public StandardGame getGame() {
      return game;
   }
}



package game.bin.gamesys;

import com.jme.scene.state.GLSLShaderObjectsState;
import com.jmex.physics.PhysicsSpace;

public class RuntimeCash {

//Graphic
private static boolean NORMAL_MAPPING = true;

private static PhysicsSpace PHYSICS_SPACE;
private static GLSLShaderObjectsState GLSL_NORMALMAP;


public RuntimeCash(){

}

public static void setPhysicsSpace(PhysicsSpace physicsspace){
PHYSICS_SPACE = physicsspace;
}

public static PhysicsSpace getPhysicsSpace(){
return PHYSICS_SPACE;
}

public static boolean getNormalMapping(){
return NORMAL_MAPPING;
}

public static void setNormalMapping(boolean NORMAL_MAPPING){
RuntimeCash.NORMAL_MAPPING = NORMAL_MAPPING;
}

public static GLSLShaderObjectsState getGLSLShaderObjectNormalMap(){
return GLSL_NORMALMAP;
}

public static void setGLSLShaderObjectNormalMap(GLSLShaderObjectsState GLSL_NORMALMAP){
RuntimeCash.GLSL_NORMALMAP = GLSL_NORMALMAP;
}
}






I'm trying to do this as well. Would love it if one of the veterans here could help.

DarkPhoenixX, i dont see a reference to a bloom render pass in your example code.



Also others wont be able to try the code you provided, because it has too many dependencies to other classes. (ResourceLocatorLibaryTool, GameApp etc.)



If you make a small example which shows the problem only, its much easier to help out.

No I cleaned the code.

Now it's only the basic skeleton (StandardGame + Physics)

(Bloom is not included, because I worked on other things and removed the code)


package Rest.test;

import com.jmex.game.StandardGame;

/**
 * Singleton wrapper for StandardGame.
 * This is needed to be able shutdown the Game when something unexpected happens.
 * @author Christoph Luder
 */
public class PhysicsGame {
   private StandardGame game = null;
   private static PhysicsGame instance = null;

   private PhysicsGame() {
      game = new StandardGame("physics fun");
   }
   /**
    * @return the PhysicsGame instance.
    */
   public static PhysicsGame get() {
      if (instance == null) {
         instance = new PhysicsGame();
      }
      return instance;
   }
   
   /**
    * @return the standardGame instance.
    */
   public StandardGame getGame() {
      return game;
   }
}



package Rest.test;

import java.net.URISyntaxException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.util.GameTaskQueueManager;
import com.jme.util.resource.ResourceLocatorTool;
import com.jme.util.resource.SimpleResourceLocator;
import com.jmex.game.state.GameStateManager;
import com.jmex.game.state.StatisticsGameState;

/**
 * The Entry point.
 * Creates a game instance, points the resource locator to the resources
 * and creates / activated the GameStates.
 * 
 * @author Christoph Luder
 */
public class Main {

   public static void main(String[] args) throws InterruptedException, ExecutionException {
      // only show Warnings
       System.setProperty("jme.stats", "set");
      Logger.getLogger("com.jme").setLevel(Level.WARNING);
      Logger.getLogger("com.jmex").setLevel(Level.WARNING);

      // create a StandardGame instance, set some settings and start it up
      PhysicsGame game = PhysicsGame.get();
      game.getGame().getSettings().setFullscreen(false);
      game.getGame().getSettings().setWidth(800);
      game.getGame().getSettings().setHeight(600);
      game.getGame().getSettings().setFramerate(60);
      game.getGame().getSettings().setVerticalSync(true);
      game.getGame().getSettings().setStencilBits(4);
      game.getGame().getSettings().setDepthBits(24);
      game.getGame().getSettings().setSamples(4);
      game.getGame().getSettings().setSFX(true);
      game.getGame().getSettings().setMusic(false);
      game.getGame().start();
      
      // set up resource search paths
      try {
         ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_AUDIO,
               new SimpleResourceLocator(Main.class.getClassLoader().getResource("com/jmedemos/physics_fun/resources/")));
         ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_TEXTURE,
               new SimpleResourceLocator(Main.class.getClassLoader().getResource("com/jmedemos/physics_fun/resources/")));
         ResourceLocatorTool.addResourceLocator(ResourceLocatorTool.TYPE_SHADER,
               new SimpleResourceLocator(Main.class.getClassLoader().getResource("com/jmedemos/physics_fun/resources/")));
      } catch (URISyntaxException e1) {
         PhysicsGame.get().getGame().finish();
      }

      // create and activate the GameStates
      GameStateManager.getInstance().attachChild(new MainGameState("main"));
//      GameTaskQueueManager.getManager().update(new Callable<Object>() {
//          public Object call() throws Exception {
//              GameStateManager.getInstance().attachChild(new StatisticsGameState());
//              return null;
//          }
//      }).get();
      GameStateManager.getInstance().activateAllChildren();
   }
}



package Rest.test;

import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.image.Texture.WrapMode;
import com.jme.input.FirstPersonHandler;
import com.jme.input.InputHandler;
import com.jme.input.KeyInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.light.DirectionalLight;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.scene.state.CullState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.CullState.Face;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.util.geom.Debugger;
import com.jme.util.resource.ResourceLocatorTool;
import com.jmex.game.state.GameStateManager;
import com.jmex.physics.DynamicPhysicsNode;
import com.jmex.physics.PhysicsDebugger;
import com.jmex.physics.StaticPhysicsNode;
import com.jmex.physics.util.PhysicsPicker;
import com.jmex.physics.util.states.PhysicsGameState;

/**
 * The main GameState.
 * Creates the Physics Playground.
 *
 * @author Christoph Luder
 */
public class MainGameState extends PhysicsGameState {
    /** reference to the camera */
   private Camera cam = DisplaySystem.getDisplaySystem().getRenderer().getCamera();
   /** we want to move the camera first person style */
   private FirstPersonHandler movementInput = null;
   /** InputHandler for the physics picker and basic command */
   private InputHandler input = new InputHandler();
   /** the static floor */
   private StaticPhysicsNode floor = null;
   /** The Node where newly created objects are attached to. */
   private Node objectsNode = null;
   
   /** should the physics debug view be rendereed */
   private boolean showPhysics = false;
   /** should the bounding boxes be rendered */
   private boolean showBounds = false;

   /** the physics picker */
   private PhysicsPicker picker = null;
   /** The physics Wind. */
   
   
   /**
    * Constructs the MainGameState.
    * Creates the scene and add the different objects to the Scenegraph.
    *
    * @param name name of the GameState
    */
   public MainGameState(String name) {
      super(name);
      
      // the node where newly created objects get attached to
      objectsNode = new Node("object Node");
      rootNode.attachChild(objectsNode);
      
      // create the scene
        picker = new PhysicsPicker( input, rootNode, getPhysicsSpace(), true);
        picker.getInputHandler().setEnabled(false);
       
        init();
        setupInput();
        setupLight();
       
        rootNode.updateGeometricState(0, true);
        rootNode.updateRenderState();
   }
   
   
   
   /**
    * Initializes the rootNodes RenderStates, initializes the Camera and
    */
   private void init() {
       // we attach transparent objects to this node, so it must be
       // rendered in the transparent bucket
       objectsNode.setRenderQueueMode(Renderer.QUEUE_TRANSPARENT);
      
       CullState cs = DisplaySystem.getDisplaySystem().getRenderer().createCullState();
       cs.setCullFace(Face.Back);
       cs.setEnabled(true);
       rootNode.setRenderState(cs);
      
       // create a first person controller to move the Camera with W,A,S,D and mouse look
       movementInput = new FirstPersonHandler(cam, 15.0f, 0.5f);
       // move the camera a bit backwards and up
       cam.setLocation(new Vector3f(2, 10, 15));
      
   }
   
   /**
    * set up some key actions.
    * - SPACE to release a new object,
    * - ESC to quit the game
    * - TAB to enable / disable the GUI GameState
    */
   private void setupInput() {
      input.addAction( new InputAction() {
         public void performAction( InputActionEvent evt ) {
            if ( evt.getTriggerPressed() ) {
               PhysicsGame.get().getGame().finish();
            }
         }
      }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_ESCAPE, InputHandler.AXIS_NONE, false );
      
      input.addAction( new InputAction() {
          public void performAction( InputActionEvent evt ) {
              if ( evt.getTriggerPressed() ) {;
              }
          }
      }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_SPACE, InputHandler.AXIS_NONE, false );
      
      input.addAction( new InputAction() {
          public void performAction( InputActionEvent evt ) {
              if ( evt.getTriggerPressed() ) {
                 if (GameStateManager.getInstance().getChild("gui").isActive()) {
                    movementInput.setEnabled(true);
                    GameStateManager.getInstance().getChild("gui").setActive(false);
               } else {
                  movementInput.setEnabled(false);
                  GameStateManager.getInstance().getChild("gui").setActive(true);
               }
              }
          }
      }, InputHandler.DEVICE_KEYBOARD, KeyInput.KEY_TAB, InputHandler.AXIS_NONE, false );
   }

   /**
    * create some light sources to illuminate the scene.
    */
   private void setupLight() {
      LightState ls = DisplaySystem.getDisplaySystem().getRenderer().createLightState();

      DirectionalLight dr1 = new DirectionalLight();
        dr1.setEnabled(true);
        dr1.setAmbient(new ColorRGBA(0, 0, 0, 0.5f));
        dr1.setDiffuse(ColorRGBA.white.clone());
        dr1.setDirection(new Vector3f(-0.2f, -0.3f, -0.4f).normalizeLocal());
        dr1.setShadowCaster(true);

        ls.attach(dr1);
      ls.setEnabled(true);
      ls.setGlobalAmbient(new ColorRGBA(0.6f, 0.6f, 0.6f, 1.0f));
      ls.setTwoSidedLighting(false);

      rootNode.setRenderState(ls);
   }

   /**
    * create a new Object.
    * The ObjectFactory create an object depending on what we selected in the GUI GameState.
    * - the new Object gets attached to the objectNode,
    * - is moved a bit in front of the camera
    * - and finally we add force to it.
    */

   /**
    * we update the input controllers and some physic object if needed,
    * then we update the physics world and call updateGeometricstate()
    * which happens in super.update().
    */
   @Override
   public void update(float tpf) {
      input.update(tpf);
//      swing.update();
      
      if (movementInput.isEnabled()) {
         movementInput.update(tpf);
      }
      super.update(tpf);
   }

   /**
    * render the scene, draw bounds or physics if needed.
    */
   @Override
   public void render(float tpf) {
       super.render(tpf);
       if (showPhysics) {
           PhysicsDebugger.drawPhysics(getPhysicsSpace(),
                   DisplaySystem.getDisplaySystem().getRenderer());
       }
      
       if (showBounds) {
           Debugger.drawBounds(getRootNode(),
                   DisplaySystem.getDisplaySystem().getRenderer());
       }
   }

    public StaticPhysicsNode getFloor() {
        return floor;
    }

    public Node getBallNode() {
        return objectsNode;
    }

   public boolean isShowPhysics() {
      return showPhysics;
   }

   public void setShowPhysics(boolean showPhysics) {
      this.showPhysics = showPhysics;
   }

    public PhysicsPicker getPicker() {
        return picker;
    }

    public void setShowBounds(boolean showBounds) {
        this.showBounds = showBounds;
    }
}

Thanks, works like a charm.

I would recommend to insert your solution in jmetest, because it's really simple!



The only problem was cs.setCullFace(Face.Back);

My loaded model has a more complex structure and suddenly some parts got invisible.

To solve this (easy) problem -->



cs.setCullFace(Face.None);

//cs.setCullFace(Face.Back);



Nevertheless thanks for your great example!

and suddenly some parts got invisible.

hmm that sounds a bit strange, are you sure your model has set a correct BoundingBox?

I will investigate this

I guess the problem is, that at some places the backside is the frontside.