package gl; import gl.scenegraph.MeshComponent; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.HashMap; import javax.microedition.khronos.opengles.GL10; import listeners.SelectionListener; import system.Setup; import util.Log; import util.Wrapper; import commands.Command; import commands.system.CommandDeviceVibrate; public class ObjectPicker { private static final int TOUCH_TAB = 1; private static final int TOUCH_DOUBLE_TAB = 2; private static final int TOUCH_LONG_PRESS = 3; private static final String LOG_TAG = "Object Picker"; private static ObjectPicker myInstance = new ObjectPicker(); /** * TODO move this to the renderer to be a little faster? */ public static boolean readyToDrawWithColor = false; /** * TODO solve problem with byte[] to string conversion for comparing. there * might also be a problem with this to string-key-concept because 0 15 10 * will be the same key as 0 151 0! */ private HashMap<String, Wrapper> myObjectLookUpTable = new HashMap<String, Wrapper>(); public int x = 0; public int y = 0; private int clickType = 0; private Command myFeedbackCommand; public void setMyFeedbackCommand(Command myFeedbackCommand) { this.myFeedbackCommand = myFeedbackCommand; } public void pickObject(GL10 gl) { readyToDrawWithColor = false; ByteBuffer pixelBuffer = ByteBuffer.allocateDirect(4); pixelBuffer.order(ByteOrder.nativeOrder()); gl.glReadPixels(x, y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuffer); byte b[] = new byte[4]; pixelBuffer.get(b); findObjectForValue(b); } private void findObjectForValue(byte[] b) { final String key = "" + b[0] + b[1] + b[2]; Wrapper wrapper = myObjectLookUpTable.get(key); Log.d(LOG_TAG, "Analysis of Pixel at " + x + ", " + y); Log.v(LOG_TAG, " > Pixelvalues: " + key); Log.v(LOG_TAG, " > Picked object: " + wrapper); if (wrapper == null && !key.equals("000")) { Log.d(LOG_TAG, " > Possible picking problem found! Trying to fix it"); wrapper = tryToFindCorrectObjectFor(b); } if (wrapper != null && wrapper.getObject() instanceof SelectionListener) { SelectionListener s = (SelectionListener) wrapper.getObject(); // Log.d("Color Picking", "Selection listener: " + s); // Log.d("Color Picking", "s.getOnClickCommand(): " + // s.getOnClickCommand()); // Log.d("Color Picking", "s.getOnDoubleClickCommand(): " + // s.getOnDoubleClickCommand()); // Log.d("Color Picking", "s.getOnLongClickCommand(): " + // s.getOnLongClickCommand()); // Log.d("Color Picking", "Click type: " + clickType); switch (clickType) { case TOUCH_TAB: { Command c = s.getOnClickCommand(); if (c != null) { giveSelectFeedbackIfEnabled(); c.execute(wrapper); } break; } case TOUCH_DOUBLE_TAB: { Command c = s.getOnDoubleClickCommand(); if (c != null) { giveSelectFeedbackIfEnabled(); c.execute(wrapper); } break; } case TOUCH_LONG_PRESS: { Command c = s.getOnLongClickCommand(); if (c != null) { giveSelectFeedbackIfEnabled(); c.execute(wrapper); } break; } } clickType = 0; } } private void giveSelectFeedbackIfEnabled() { if (myFeedbackCommand != null) myFeedbackCommand.execute(); } private Wrapper tryToFindCorrectObjectFor(byte[] b) { /* * The problem with color calculation is, that its done by the gpu (this * is my guess) and different devices will return different values for * the same color. eg on the Motorola Milestone the order has to be * key=b[3] b[2] b[1] and rounding by the gpu seems to be buggy too on * many devices */ for (int i = -1; i < 2; i++) { for (int i2 = -1; i2 < 2; i2++) { for (int i3 = -1; i3 < 2; i3++) { { String k = getKey(b[0] + i, b[1] + i2, b[2] + i3); // Log.d("Color Picking", "possible key=" + k); Wrapper w = myObjectLookUpTable.get(k); if (w != null) { Log.d(LOG_TAG, "Solution found. Modifing key to " + k); /* * Actually the key isn't modified, the wrapper is * just registered with the other value too, so that * the Wrapper will be found the next time */ myObjectLookUpTable .put(getKey(b[0], b[1], b[2]), w); return w; } } { String k = getKey(b[2] + i3, b[1] + i2, b[0] + i); // Log.d("Color Picking", "possible key=" + k); Wrapper w = myObjectLookUpTable.get(k); if (w != null) { Log.d(LOG_TAG, "Solution found. Modifing key to " + k); /* * Actually the key isn't modified, the wrapper is * just registered with the other value too, so that * the Wrapper will be found the next time */ myObjectLookUpTable .put(getKey(b[0], b[1], b[2]), w); return w; } } } } } Log.d(LOG_TAG, "No solution for picking problem found :("); return null; } private String getKey(int a, int b, int c) { return "" + (byte) a + (byte) b + (byte) c; } /** * @return the instance of the singelton */ public static ObjectPicker getInstance() { return myInstance; } public void setClickPosition(float x, float y) { clickType = TOUCH_TAB; setClick(x, y); } public void setLongClickPosition(float x, float y) { clickType = TOUCH_LONG_PRESS; setClick(x, y); } public void setDoubleClickPosition(float x, float y) { clickType = TOUCH_DOUBLE_TAB; setClick(x, y); } private void setClick(float x, float y) { this.x = (int) x; this.y = (int) y; readyToDrawWithColor = true; } /** * @param info * @param prefferedColor * if the object has an own color than set the key color near the * preffered color (would be the myColor attribute of a * {@link MeshComponent} normaly) * @return the unique color the identify the object later */ public Color registerMesh(Wrapper info, Color prefferedColor) { Color myPickColor = getBestColor(prefferedColor); byte[] b = getByteArrayFromColor(myPickColor); String key = "" + b[0] + b[1] + b[2]; Log.v(LOG_TAG, " > New Color byte[]: {" + b[0] + ", " + b[1] + ", " + b[2] + ", " + b[3] + "}"); Log.v(LOG_TAG, " > New Color key: " + key); myObjectLookUpTable.put(key, info); return myPickColor; } /** * TODO write better algo? if alpha is set everything is darker as normal * color so consider this * * @param x * @return */ private Color getBestColor(Color x) { Color c; boolean endlessLoop = false; if (x != null) { c = new Color(x.red, x.green, x.blue, 1); } else { c = new Color(0, 0, 0.01f, 1); } while (isAlreadyTaken(c)) { if (c.red < 1) c.red += 0.01f; else if (c.green < 1) { c.green += 0.01f; c.red = 0; } else if (c.blue < 1) { c.blue += 0.01f; c.red = 0; c.green = 0; } else if (!endlessLoop) { c.blue = 0; c.red = 0; c.green = 0; endlessLoop = true; } else { Log.e(LOG_TAG, "Woot.. All picking colors were taken.. and there are really a lot of colors.. double rainbow"); return new Color(0, 0, 0, 0); } } return c; } private boolean isAlreadyTaken(Color c) { byte[] b = getByteArrayFromColor(c); String key = "" + b[0] + b[1] + b[2]; if (myObjectLookUpTable.get(key) != null) return true; return false; } public static byte[] getByteArrayFromColor(Color c) { final byte[] b = new byte[4]; boolean isOld = Setup.isOldDeviceWhereNothingWorksAsExpected; b[0] = floatToByteColorValue(c.red, isOld); b[1] = floatToByteColorValue(c.green, isOld); b[2] = floatToByteColorValue(c.blue, isOld); b[3] = floatToByteColorValue(c.alpha, isOld); return b; } /** * the float value (from 0.0f to 1.0f) has to be mapped to the int value (0 * to 255) which then is converted to byte (for example 255=-1 in byte) * * @param f * @return */ public static byte floatToByteColorValue(float f, boolean oldDevice) { /* * TODO: * * currently this method does 0.895 -> -28 and opengl returns -27 * * -28 = 0xe4 in signed byte = 228 decimal if you treat the 0xe4 as * unsigned. 228/255 = 0.894 in float * * fix this and get -27 returned instead of -28 */ if (oldDevice) { /* * this is a bug-fix which is necessary due to rounding errors on * older devices another part of this bugfix is the * tryToFindCorrectObjectFor method */ if (f == 1) return -1; return (byte) (f * 256f); } return (byte) (f * 255f); } /** * @param feedbackCommand * this command will be executed every time a successful click * appeared. Use a {@link CommandDeviceVibrate} here e.g. */ public static void resetInstance(Command feedbackCommand) { myInstance = new ObjectPicker(); myInstance.setMyFeedbackCommand(feedbackCommand); } /** * 565 format: RRRR RGGG GGGB BBBB <br> * * @param f * @return */ public static float rgb565to888(float f) { return 1 << 1; } }