/* * Copyright © 2012 Nokia Corporation. All rights reserved. * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation. * Oracle and Java are trademarks or registered trademarks of Oracle and/or its * affiliates. Other product and company names mentioned herein may be trademarks * or trade names of their respective owners. * See LICENSE.TXT for license information. */ package com.nokia.example.amaze.ui; import java.util.Enumeration; import java.util.Vector; /** * Copyright (c) 2012 Nokia Corporation. */ import javax.microedition.lcdui.Image; import javax.microedition.m3g.Appearance; import javax.microedition.m3g.Background; import javax.microedition.m3g.CompositingMode; import javax.microedition.m3g.Image2D; import javax.microedition.m3g.Mesh; import javax.microedition.m3g.Node; import javax.microedition.m3g.PolygonMode; import javax.microedition.m3g.Texture2D; import javax.microedition.m3g.Transform; import javax.microedition.m3g.World; import com.nokia.example.amaze.Main; import com.nokia.example.amaze.model.GameModel; import com.nokia.example.amaze.model.MarbleModel; import com.nokia.example.amaze.model.Maze; /** * Helper class containing methods to create the world content. This * functionality is separated into its own class so that MazeCanvas * wouldn't be so full. */ public class WorldBuilder { // Constants private static final int MAX_NODES_TO_DESTROY_COUNT = 70; // Filenames of graphical assets private static final String BACKGROUND_IMAGE_FILENAME = "/graphics/background.png"; private static final String MARBLE_IMAGE_FILENAME = "/graphics/marble.png"; private static final String FLOOR_IMAGE_FILENAME = "/graphics/floor.png"; private static final String WALL_IMAGE_FILENAME = "/graphics/wall.png"; private static final String GOAL_IMAGE_FILENAME = "/graphics/goal.png"; // Members private Node[] _nodesToDestroy = null; /** * Constructor. */ public WorldBuilder() { _nodesToDestroy = new Node[MAX_NODES_TO_DESTROY_COUNT]; } /** * Sets up the maze. * @param world * @param maze * @param wallAppearance * @param wallClearAppearance */ public void createNewMaze(World world, Maze maze, Appearance wallAppearance) { // Destroy the previous maze if one exists destroyMaze(world); // Generate a new maze maze.createNew(GameModel.MAZE_CORRIDOR_COUNT); // Create the planes based on the generated maze Enumeration wallsEnum = createPlanes(maze); // Release resources not needed anymore maze.clear(); // Create meshes of the planes and add them to the world while (wallsEnum.hasMoreElements()) { Mesh wallMesh = ((Plane)wallsEnum.nextElement()).createMesh(); wallMesh.setAppearance(0, wallAppearance); world.addChild(wallMesh); addNodeToDestroy(wallMesh); } // Create the goal mark to the end of the maze createGoalMark(world, maze); } /** * Creates the background. * @param world * @return The created background instance. */ public Background createBackground(World world) { Background background = new Background(); Image backgroundImage = Main.makeImage(BACKGROUND_IMAGE_FILENAME); if (backgroundImage != null) { background.setImage(new Image2D(Image2D.RGB, backgroundImage)); background.setImageMode(Background.REPEAT, Background.REPEAT); world.setBackground(background); } return background; } /** * Creates the wall appearances. * @param wallAppearance * @param wallClearAppearance */ public void createWallAppearances(Appearance wallAppearance, Appearance wallClearAppearance) { // The walls need perspective correction enabled PolygonMode wallPolygonMode = new PolygonMode(); wallPolygonMode.setPerspectiveCorrectionEnable(true); // Build the wall semi-transparent appearance wallClearAppearance.setPolygonMode(wallPolygonMode); // This is to make a wall semi-transparent CompositingMode wallClearCompositeMode = new CompositingMode(); wallClearCompositeMode.setBlending(CompositingMode.ALPHA_ADD); wallClearAppearance.setCompositingMode(wallClearCompositeMode); // Build the normal wall appearance wallAppearance.setPolygonMode(wallPolygonMode); // Load the wall texture Image wallTextureImage = Main.makeImage(WALL_IMAGE_FILENAME); if (wallTextureImage != null) { Texture2D wallTexture = null; wallTexture = new Texture2D(new Image2D(Image2D.RGB, wallTextureImage)); // The texture is repeated wallTexture.setWrapping(Texture2D.WRAP_REPEAT, Texture2D.WRAP_REPEAT);//Texture2D.WRAP_CLAMP); wallTexture.setBlending(Texture2D.FUNC_REPLACE); wallTexture.setFiltering(Texture2D.FILTER_LINEAR, Texture2D.FILTER_NEAREST); // Set the texture wallAppearance.setTexture(0, wallTexture); wallClearAppearance.setTexture(0, wallTexture); } } /** * Creates the floor. * @param world */ public void createFloor(World world) { float floorSide = GameModel.MAZE_SIDE_LENGTH / 2; // define the location and size of the floor Transform floorTransform = new Transform(); floorTransform.postRotate(90.0f, -1.0f, 0.0f, 0.0f); floorTransform.postScale(floorSide, floorSide, 1.0f); // The floor appearance. Basically a texture repeated many times Appearance floorAppearance = new Appearance(); // The floor needs that perspective correction is enabled PolygonMode floorPolygonMode = new PolygonMode(); floorPolygonMode.setPerspectiveCorrectionEnable(true); floorAppearance.setPolygonMode(floorPolygonMode); // load the texture Texture2D floorTexture = null; Image floorTextureImage = Main.makeImage(FLOOR_IMAGE_FILENAME); if (floorTextureImage != null) { floorTexture = new Texture2D( new Image2D(Image2D.RGB, floorTextureImage)); // the texture is repeated many times floorTexture.setWrapping(Texture2D.WRAP_REPEAT, Texture2D.WRAP_REPEAT); floorTexture.setBlending(Texture2D.FUNC_REPLACE); floorTexture.setFiltering(Texture2D.FILTER_LINEAR, Texture2D.FILTER_NEAREST); floorAppearance.setTexture(0, floorTexture); } Plane floor = new Plane(floorTransform, 10); // build the mesh Mesh floorMesh = floor.createMesh(); floorMesh.setAppearance(0, floorAppearance); // the floor is not pickable floorMesh.setPickingEnable(false); // add to the world world.addChild(floorMesh); } /** * Creates the goal mark. * @param world * @param maze */ private void createGoalMark(World world, Maze maze) { Appearance appearance = new Appearance(); CompositingMode compositingMode = new CompositingMode(); compositingMode.setBlending(CompositingMode.ALPHA); appearance.setCompositingMode(compositingMode); Texture2D texture = null; Image textureImage = Main.makeImage(GOAL_IMAGE_FILENAME); if (textureImage != null) { texture = new Texture2D( new Image2D(Image2D.RGBA, textureImage)); // The texture is not repeated texture.setWrapping(Texture2D.WRAP_CLAMP, Texture2D.WRAP_CLAMP); texture.setBlending(Texture2D.FUNC_REPLACE); texture.setFiltering(Texture2D.FILTER_NEAREST, Texture2D.FILTER_NEAREST); appearance.setTexture(0, texture); } // Create the goal mesh Plane goalMarkPlane = createGoalMark(maze); Mesh goalMarkMesh = goalMarkPlane.createMesh(); goalMarkMesh.setAppearance(0, appearance); goalMarkMesh.setPickingEnable(false); // Not pickable world.addChild(goalMarkMesh); addNodeToDestroy(goalMarkMesh); } /** * Creates the marble. * @param world * @return The newly created marble as a Mesh instance. */ public Mesh createMarble(World world) { Transform transform = new Transform(); transform.postRotate(90.0f, -1.0f, 0.0f, 0.0f); transform.postScale(MarbleModel.DEFAULT_SIZE, MarbleModel.DEFAULT_SIZE, 1.0f); Appearance appearance = new Appearance(); CompositingMode compositingMode = new CompositingMode(); compositingMode.setBlending(CompositingMode.ALPHA); appearance.setCompositingMode(compositingMode); Texture2D texture = null; Image marbleImage = Main.makeImage(MARBLE_IMAGE_FILENAME); if (marbleImage != null) { texture = new Texture2D(new Image2D(Image2D.RGBA, marbleImage)); // The texture is not repeated texture.setWrapping(Texture2D.WRAP_CLAMP, Texture2D.WRAP_CLAMP); texture.setBlending(Texture2D.FUNC_REPLACE); texture.setFiltering(Texture2D.FILTER_NEAREST, Texture2D.FILTER_NEAREST); appearance.setTexture(0, texture); } Plane marblePlane = new Plane(transform, 1); Mesh marble = marblePlane.createMesh(); marble.setAppearance(0, appearance); marble.setRenderingEnable(false); marble.setPickingEnable(false); world.addChild(marble); return marble; } /** * Destroys the maze planes and goal mark. * @param world The world from which to remove the nodes. */ public void destroyMaze(World world) { if (_nodesToDestroy != null) { for (int i = 0; i < MAX_NODES_TO_DESTROY_COUNT; ++i) { if (_nodesToDestroy[i] != null) { world.removeChild(_nodesToDestroy[i]); } } _nodesToDestroy = null; } } /** * Adds a reference of the given node to the internal array so that it can * be removed from the world instance and deleted later (when a new maze * needs to be generated). * @param node The node to destroy later. */ private boolean addNodeToDestroy(Node node) { if (_nodesToDestroy == null) { _nodesToDestroy = new Node[MAX_NODES_TO_DESTROY_COUNT]; _nodesToDestroy[0] = node; return true; } for (int i = 0; i < MAX_NODES_TO_DESTROY_COUNT; ++i) { if (_nodesToDestroy[i] == null) { _nodesToDestroy[i] = node; return true; } } // The array is full System.out.println("WorldBuilder::addNodeToDestroy(): The array is full!"); return false; } /** * Creates a plane located at the end of the maze. * @return The goal plane. */ private Plane createGoalMark(Maze maze) { Transform markTransform = new Transform(); markTransform.postTranslate(maze.origin() + maze.goalX() * maze.spaceBetweenPlanes(), maze.height() / 2 + 0.2f, -maze.origin() - 5f); markTransform.postScale(10f, 10f, 10f); markTransform.postRotate(90f, -1f, 0f, 0f); return new Plane(markTransform, 1f); } /** * Creates the horizontal and vertical planes and puts them in an * enumeration. Note that after calling this method the maze array becomes * unusable as it is released (set to null). * @param maze The model of the maze. * @return The enumeration of the components in the planes vector. */ private Enumeration createPlanes(Maze maze) { long[] mazeArray = maze.array(); if (mazeArray == null || mazeArray.length == 0) { return null; } float spaceBetweenPlanes = maze.spaceBetweenPlanes(); float mazeOrigin = maze.origin(); float mazeHeight = maze.height(); Vector allPlanes = new Vector(); for (int i = 0; i < mazeArray.length; i++) { int startX = -1; for (int j = 0; j < mazeArray.length; j++) { long shift = (0x1L << j); if ((mazeArray[i] & shift) == shift && startX == -1) { startX = j; continue; } if ((((mazeArray[i] & shift) == 0) || (j == (mazeArray.length - 1))) && (startX >= 0)) { int steps = j - startX; // Don't create walls of side 1 since they will be created // on the other direction if (steps == 1) { startX = -1; continue; } // compensate that the last item is always 1 if (j == (mazeArray.length - 1)) { steps++; } Transform planeTransform = new Transform(); // Divided by 2 since the original square is of side 2 float wallWidth = (maze.spaceBetweenPlanes() * (steps - 1) / 2); // Move to the correct position planeTransform.postTranslate( mazeOrigin + spaceBetweenPlanes * startX + wallWidth, mazeHeight, mazeOrigin + spaceBetweenPlanes * i); // Give the actual size planeTransform.postScale(wallWidth, mazeHeight, 1f); allPlanes.addElement(new Plane(planeTransform, 1f)); startX = -1; } } } for (int i = 0; i < mazeArray.length; i++) { int startY = -1; long shift = (0x1L << i); for (int j = 0; j < mazeArray.length; j++) { if ((mazeArray[j] & shift) == shift && startY == -1) { startY = j; continue; } if ((((mazeArray[j] & shift) == 0) || (j == (mazeArray.length - 1))) && (startY >= 0)) { int steps = j - startY; if (steps == 1) { startY = -1; continue; } if (j == (mazeArray.length - 1)) { steps++; } Transform planeTransform = new Transform(); // Divided by 2 since the original square is of side 2 float wallWidth = (spaceBetweenPlanes * (steps - 1) / 2); // Translate to the correct position planeTransform.postTranslate( mazeOrigin + spaceBetweenPlanes * i, mazeHeight, mazeOrigin + spaceBetweenPlanes * startY + wallWidth); // Rotate 90 degrees since this is a vertical wall planeTransform.postRotate(90f, 0f, 1f, 0f); // Give the correct size planeTransform.postScale(wallWidth, mazeHeight, 1f); allPlanes.addElement(new Plane(planeTransform, 1f)); startY = -1; } } } return allPlanes.elements(); } }