/* Copyright (C) 2007 The Android Open Source Project */ #include "quakedef.h" unsigned short d_8to16table[256]; unsigned d_8to24table[256]; #ifdef SUPPORT_8BIT_MIPMAPGENERATION unsigned char d_15to8table[65536]; #endif cvar_t mouse_button_commands[3] = { CVAR2("mouse1","+attack"), CVAR2("mouse2","+strafe"), CVAR2("mouse3","+forward"), }; static const int MOUSE_LEFTBUTTON = 1; static const int MOUSE_MIDDLEBUTTON = 2; static const int MOUSE_RIGHTBUTTON = 4; bool mouse_tap; float mouse_x, mouse_y; float old_mouse_x, old_mouse_y; int mx, my; bool mouse_buttonstate; bool mouse_oldbuttonstate; cvar_t m_filter = CVAR2("m_filter","1"); int scr_width, scr_height; cvar_t _windowed_mouse = CVAR3("_windowed_mouse","0", true); /*-----------------------------------------------------------------------*/ //int texture_mode = GL_NEAREST; //int texture_mode = GL_NEAREST_MIPMAP_NEAREST; //int texture_mode = GL_NEAREST_MIPMAP_LINEAR; int texture_mode = GL_LINEAR; // int texture_mode = GL_LINEAR_MIPMAP_NEAREST; //int texture_mode = GL_LINEAR_MIPMAP_LINEAR; int texture_extension_number = 1; float gldepthmin, gldepthmax; cvar_t gl_ztrick = CVAR2("gl_ztrick","0"); const char *gl_vendor; const char *gl_renderer; const char *gl_version; const char *gl_extensions; qboolean is8bit = false; qboolean isPermedia = false; qboolean gl_mtexable = false; /*-----------------------------------------------------------------------*/ void D_BeginDirectRect (int x, int y, byte *pbitmap, int width, int height) { } void D_EndDirectRect (int x, int y, int width, int height) { } void VID_Shutdown(void) { } void VID_ShiftPalette(unsigned char *p) { // VID_SetPalette(p); } void VID_SetPalette (unsigned char *palette) { byte *pal; unsigned r,g,b; unsigned v; int r1,g1,b1; int k; unsigned short i; unsigned *table; FILE *f; char s[255]; int dist, bestdist; static qboolean palflag = false; PMPBEGIN(("VID_SetPalette")); // // 8 8 8 encoding // Con_Printf("Converting 8to24\n"); pal = palette; table = d_8to24table; for (i=0 ; i<256 ; i++) { r = pal[0]; g = pal[1]; b = pal[2]; pal += 3; // v = (255<<24) + (r<<16) + (g<<8) + (b<<0); // v = (255<<0) + (r<<8) + (g<<16) + (b<<24); v = (255<<24) + (r<<0) + (g<<8) + (b<<16); *table++ = v; } d_8to24table[255] &= 0xffffff; // 255 is transparent #ifdef SUPPORT_8BIT_MIPMAPGENERATION // JACK: 3D distance calcs - k is last closest, l is the distance. // FIXME: Precalculate this and cache to disk. if (palflag) return; palflag = true; COM_FOpenFile("glquake/15to8.pal", &f); if (f) { fread(d_15to8table, 1<<15, 1, f); fclose(f); } else { PMPBEGIN(("Creating 15to8 palette")); for (i=0; i < (1<<15); i++) { /* Maps 0000.0000.0000.0000 0000.0000.0001.1111 = Red = 0x001F 0000.0011.1110.0000 = Green = 0x03E0 0111.1100.0000.0000 = Blue = 0x7C00 */ r = ((i & 0x1F) << 3)+4; g = ((i & 0x03E0) >> (5-3)) +4; b = ((i & 0x7C00) >> (10-3))+4; pal = (unsigned char *)d_8to24table; for (v=0,k=0,bestdist=0x7FFFFFFF; v<256; v++,pal+=4) { r1 = (int)r - (int)pal[0]; g1 = (int)g - (int)pal[1]; b1 = (int)b - (int)pal[2]; dist = ((r1*r1)+(g1*g1)+(b1*b1)); if (dist < bestdist) { k=v; bestdist = dist; } } d_15to8table[i]=k; } PMPEND(("Creating 15to8 palette")); sprintf(s, "%s/glquake", com_gamedir); Sys_mkdir (s); sprintf(s, "%s/glquake/15to8.pal", com_gamedir); if ((f = fopen(s, "wb")) != NULL) { fwrite(d_15to8table, 1<<15, 1, f); fclose(f); } else { Con_Printf("Could not write %s\n", s); } } #endif // SUPPORT_8BIT_MIPMAPGENERATION PMPEND(("VID_SetPalette")); } /* =============== GL_Init =============== */ void GL_Init (void) { gl_vendor = (char*) glGetString (GL_VENDOR); Con_Printf ("GL_VENDOR: %s\n", gl_vendor); GLCHECK("glGetString"); gl_renderer = (char*) glGetString (GL_RENDERER); Con_Printf ("GL_RENDERER: %s\n", gl_renderer); GLCHECK("glGetString"); gl_version = (char*) glGetString (GL_VERSION); Con_Printf ("GL_VERSION: %s\n", gl_version); GLCHECK("glGetString"); gl_extensions = (char*) glGetString (GL_EXTENSIONS); Con_Printf ("GL_EXTENSIONS: %s\n", gl_extensions); GLCHECK("glGetString"); // Con_Printf ("%s %s\n", gl_renderer, gl_version); // Enable/disable multitexture: gl_mtexable = true; glClearColor (1,0,0,0); glCullFace(GL_FRONT); glEnable(GL_TEXTURE_2D); glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.666); #ifdef USE_OPENGLES #else glPolygonMode (GL_FRONT_AND_BACK, GL_FILL); #endif glShadeModel(GL_FLAT); glDisable(GL_DITHER); // perspective correction don't work well currently glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); #ifdef USE_OPENGLES glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); #endif } /* ================= GL_BeginRendering ================= */ void GL_BeginRendering (int *x, int *y, int *width, int *height) { extern cvar_t gl_clear; *x = *y = 0; *width = scr_width; *height = scr_height; // if (!wglMakeCurrent( maindc, baseRC )) // Sys_Error ("wglMakeCurrent failed"); // glViewport (*x, *y, *width, *height); } void GL_EndRendering (void) { //glFlush(); // !!! Swap buffers. } void Init_KBD(void) { } // This function controls whether or not 8-bit paletted textures are used: qboolean VID_Is8bit(void) { return 0; } static void Check_Gamma (unsigned char *pal) { float vid_gamma; float f, inf; unsigned char palette[768]; int i; if ((i = COM_CheckParm("-gamma")) == 0) { vid_gamma = 0.5; // brighten up game. } else vid_gamma = Q_atof(com_argv[i+1]); if(vid_gamma != 1) { for (i=0 ; i<768 ; i++) { f = pow ( (pal[i]+1)/256.0 , vid_gamma ); inf = f*255 + 0.5; if (inf < 0) inf = 0; if (inf > 255) inf = 255; palette[i] = (unsigned char) inf; } } memcpy (pal, palette, sizeof(palette)); } void VID_Init(unsigned char *palette) { int i; GLint attribs[32]; char gldir[MAX_OSPATH]; int width = scr_width, height = scr_height; S_Init(); Init_KBD(); Cvar_RegisterVariable (&gl_ztrick); vid.maxwarpwidth = scr_width; vid.maxwarpheight = height; vid.colormap = host_colormap; vid.fullbright = 0xffff; vid.aspect = (float) scr_width / (float) scr_height; vid.numpages = 2; vid.rowbytes = 2 * scr_width; vid.width = scr_width; vid.height = scr_height; vid.conwidth = scr_width; vid.conheight = scr_height; // interpret command-line params // set vid parameters GL_Init(); sprintf (gldir, "%s/glquake", com_gamedir); Sys_mkdir (gldir); Check_Gamma(palette); VID_SetPalette(palette); Con_SafePrintf ("Video mode %dx%d initialized.\n", width, height); vid.recalc_refdef = 1; // force a surface cache flush } // Android Key event codes. Some of these are // only generated from the simulator. // Not all Android devices can generate all codes. byte scantokey[] = { '$', K_ESCAPE, '$', '$', K_ESCAPE, '$', '$', '0', // 0.. 7 '1', '2', '3', '4', '5', '6', '7', '8', // 8..15 '9', '$', '$', K_UPARROW, K_DOWNARROW, K_LEFTARROW, K_RIGHTARROW, K_ENTER, // 16..23 '$', '$', '$', '$', '$', 'a', 'b', 'c', // 24..31 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', // 32..39 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', // 40..47 't', 'u', 'v', 'w', 'x', 'y', 'z', ',', // 48..55 '.', K_CTRL, K_SHIFT, K_TAB, ' ', '$', '$', '$', // 56..63 '$', '$', K_ENTER, K_BACKSPACE, '`', '-', '=', '[', // 64..71 ']', '\\', ';', '\'', '/', '@', '#', '$', // 72..79 '$', '$', K_ESCAPE, '$' // 80..83 }; byte scantokeyAlt[] = { 0, 0, 0, 0, 0, 0, 0, 0, // 0.. 7 0, 0, 0, 0, 0, 0, 0, 0, // 8..15 0, 0, 0, 0, 0, 0, 0, 0, // 16..23 0, 0, 0, 0, 0, '%', '=', '8', // 24..31 '5', '2', '6', '-', '[', '$', ']', '"', // 32..39 '\'', '>', '<', '(', ')', '*', '3', '4', // 40..47 '+', '&', '9', '1', '7', '!', '#', ';', // 48..55 ':', 0, 0, 0, K_TAB, 0, 0, 0, // 56..63 0, 0, 0, 0, 0, 0, 0, 0, // 64..71 0, 0, '?', '0', 0, 0, 0, 0, // 72..79 0, 0, K_ESCAPE, 0 // 80..83 }; #if 0 byte scantokeyCap[] = { 0, 0, 0, 0, 0, 0, 0, 0, // 0.. 7 0, 0, 0, 0, 0, 0, 0, 0, // 8..15 0, 0, 0, 0, 0, 0, 0, 0, // 16..23 0, 0, 0, 0, 0, 'A', 'B', 'C', // 24..31 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', // 32..39 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', // 40..47 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 0, // 48..55 0, 0, 0, 0, 0, 0, 0, 0, // 56..63 0, 0, 0, 0, 0, 0, 0, 0, // 64..71 0, 0, 0, 0, 0, 0, 0, 0, // 72..79 0, 0, K_ESCAPE, 0 // 80..83 }; #endif byte scantokeySym[] = { 0, 0, 0, 0, 0, 0, 0, 0, // 0.. 7 0, 0, 0, 0, 0, 0, 0, 0, // 8..15 0, 0, 0, 0, 0, 0, 0, 0, // 16..23 0, 0, 0, 0, 0, 0, 0, K_F8, // 24..31 K_F5, K_F2, K_F6, '_', 0, 0, 0, 0, // 32..39 0, 0, 0, 0, 0, 0, K_F3, K_F4, // 40..47 K_F11, 0, K_F9, K_F1, K_F7, K_F12, K_PAUSE, 0, // 48..55 0, 0, 0, 0, 0, 0, 0, 0, // 56..63 0, 0, 0, 0, 0, 0, 0, 0, // 64..71 0, 0, '`', K_F10, 0, 0, 0, 0, // 72..79 0, 0, K_ESCAPE, 0 // 80..83 }; #define ALT_KEY_VALUE 57 // #define CAPS_KEY_VALUE 58 #define SYM_KEY_VALUE 61 byte modifierKeyInEffect; qboolean symKeyDown; byte symKeyCode; // Called from stand-alone main() function to process an event. // Return non-zero if the event is handled. int AndroidEvent(int type, int value) { if(value >= 0 && value < (int) sizeof(scantokey)) { byte key; qboolean isPress = type != 0; qboolean isModifier = value == ALT_KEY_VALUE || value == SYM_KEY_VALUE; if(isModifier) { if(isPress) { if(modifierKeyInEffect == value) { // Press modifier twice to cancel modifier modifierKeyInEffect = 0; } else { // Most recent modifier key wins modifierKeyInEffect = value; } } return 1; } else { switch(modifierKeyInEffect) { default: key = scantokey[value]; break; case ALT_KEY_VALUE: key = scantokeyAlt[value]; break; // case CAP_KEY_VALUE: key = scantokeyCap[value]; break; case SYM_KEY_VALUE: key = scantokeySym[value]; break; } if(!key) { key = scantokey[value]; } // Hack: Remap @ and / to K_CTRL in game mode if(key_dest == key_game && ! modifierKeyInEffect && (key == '@' || key == '/')) { key = K_CTRL; } if(!isPress) { modifierKeyInEffect = 0; } } Key_Event(key, type); // PMPLOG(("type: %d, value: %d -> %d '%c'\n", type, value, key, key)); return 1; } else { PMPWARNING(("unexpected event type: %d, value: %d\n", type, value)); } return 0; } // Called from Java to process an event. // Return non-zero if the event is handled. #if !defined(__clang__) int AndroidEvent2(int type, int value) #else extern "C" int AndroidEvent2_LLVM(int type, int value) #endif { Key_Event(value, type); return 0; } static const int MOTION_DOWN = 0; static const int MOTION_UP = 1; static const int MOTION_MOVE = 2; static const int MOTION_CANCEL = 3; class GestureDetector { private: bool mIsScroll; bool mIsTap; bool mIsDoubleTap; float mScrollX; float mScrollY; static const unsigned long long TAP_TIME_MS = 200; static const unsigned long long DOUBLE_TAP_TIME_MS = 400; static const int TAP_REGION_MANHATTAN_DISTANCE = 10; bool mAlwaysInTapRegion; float mDownX; float mDownY; unsigned long long mDownTime; unsigned long long mPreviousDownTime; /** * Position of the last motion event. */ float mLastMotionY; float mLastMotionX; public: /** * Analyze a motion event. Call this once for each motion event * that is received by a view. * @param ev the motion event to analyze. */ void analyze(unsigned long long eventTime, int action, float x, float y, float pressure, float size, int deviceId) { mIsScroll = false; mIsTap = false; mIsDoubleTap = false; switch (action) { case MOTION_DOWN: printf("Down"); // Remember where the motion event started mLastMotionX = x; mLastMotionY = y; mDownX = x; mDownY = y; mPreviousDownTime = mDownTime; mDownTime = eventTime; mAlwaysInTapRegion = true; break; case MOTION_MOVE: { mIsScroll = true; mScrollX = mLastMotionX - x; mScrollY = mLastMotionY - y; mLastMotionX = x; mLastMotionY = y; int manhattanTapDistance = (int) (absf(x - mDownX) + absf(y - mDownY)); if (manhattanTapDistance > TAP_REGION_MANHATTAN_DISTANCE) { mAlwaysInTapRegion = false; } } break; case MOTION_UP: { unsigned long long doubleTapDelta = eventTime - mPreviousDownTime; unsigned long long singleTapDelta = eventTime - mDownTime; if (mAlwaysInTapRegion) { if (doubleTapDelta < DOUBLE_TAP_TIME_MS) { mIsDoubleTap = true; } else if (singleTapDelta < TAP_TIME_MS) { mIsTap = true; } } } break; } } /** * @return true if the current motion event is a scroll * event. */ bool isScroll() { return mIsScroll; } /** * This value is only defined if {@link #isScroll} is true. * @return the X position of the current scroll event. * event. */ float scrollX() { return mScrollX; } /** * This value is only defined if {@link #isScroll} is true. * @return the Y position of the current scroll event. * event. */ float scrollY() { return mScrollY; } /** * @return true if the current motion event is a single-tap * event. */ bool isTap() { return mIsTap; } /** * This value is only defined if either {@link #isTap} or * {@link #isDoubleTap} is true. * @return the X position of the current tap event. * event. */ float tapX() { return mDownX; } /** * This value is only defined if either {@link #isTap} or * {@link #isDoubleTap} is true. * @return the Y position of the current tap event. * event. */ float tapY() { return mDownY; } /** * @return true if the current motion event is a double-tap * event. */ bool isDoubleTap() { return mIsDoubleTap; } private: inline float absf(float a) { return a >= 0.0f ? a : -a; } }; GestureDetector gGestureDetector; #if !defined(__clang__) int AndroidMotionEvent #else extern "C" int AndroidMotionEvent_LLVM #endif (unsigned long long eventTime, int action, float x, float y, float pressure, float size, int deviceId) { gGestureDetector.analyze(eventTime, action, x, y, pressure, size, deviceId); if (gGestureDetector.isTap()) { mouse_tap = true; } else if (gGestureDetector.isScroll()) { mx += (int) gGestureDetector.scrollX(); my += (int) gGestureDetector.scrollY(); } return true; } #if !defined(__clang__) int AndroidTrackballEvent #else extern "C" int AndroidTrackballEvent_LLVM #endif (unsigned long long eventTime, int action, float x, float y) { switch (action ) { case MOTION_DOWN: mouse_buttonstate = true; break; case MOTION_UP: mouse_buttonstate = false; break; case MOTION_MOVE: mx += (int) (20.0f * x); my += (int) (20.0f * y); break; } return true; } void Sys_SendKeyEvents(void) { // Used to poll keyboards on systems that need to poll keyboards. } void Force_CenterView_f (void) { cl.viewangles[PITCH] = 0; } void IN_Init(void) { Cvar_RegisterVariable (&mouse_button_commands[0]); Cvar_RegisterVariable (&mouse_button_commands[1]); Cvar_RegisterVariable (&mouse_button_commands[2]); Cmd_AddCommand ("force_centerview", Force_CenterView_f); } void IN_Shutdown(void) { } /* =========== IN_Commands =========== */ void IN_Commands (void) { // perform button actions if (mouse_tap) { Key_Event (K_MOUSE1, true); Key_Event (K_MOUSE1, false); mouse_tap = false; } if (mouse_buttonstate != mouse_oldbuttonstate) { Key_Event (K_MOUSE1, mouse_buttonstate ? true : false); mouse_oldbuttonstate = mouse_buttonstate; } } /* =========== IN_Move =========== */ void IN_MouseMove (usercmd_t *cmd) { #if 0 if (m_filter.value) { mouse_x = (mx + old_mouse_x) * 0.5; mouse_y = (my + old_mouse_y) * 0.5; } else #endif { mouse_x = mx; mouse_y = my; } old_mouse_x = mx; old_mouse_y = my; mx = my = 0; // clear for next update mouse_x *= 5.0f * sensitivity.value; mouse_y *= 5.0f * sensitivity.value; // add mouse X/Y movement to cmd if ( (in_strafe.state & 1) || (lookstrafe.value && (in_mlook.state & 1) )) cmd->sidemove += m_side.value * mouse_x; else cl.viewangles[YAW] -= m_yaw.value * mouse_x; if (in_mlook.state & 1) V_StopPitchDrift (); if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) { cl.viewangles[PITCH] += m_pitch.value * mouse_y; if (cl.viewangles[PITCH] > 80) cl.viewangles[PITCH] = 80; if (cl.viewangles[PITCH] < -70) cl.viewangles[PITCH] = -70; } else { if ((in_strafe.state & 1) && noclip_anglehack) cmd->upmove -= m_forward.value * mouse_y; else cmd->forwardmove -= m_forward.value * mouse_y; } } void IN_Move (usercmd_t *cmd) { IN_MouseMove(cmd); }