Hillcrest Labs Loop support by rotajota
authorparasti <parasti@78b8d119-cf0a-0410-b17c-f493084dd1d7>
Thu, 11 Feb 2010 22:02:33 +0000 (22:02 +0000)
committerparasti <parasti@78b8d119-cf0a-0410-b17c-f493084dd1d7>
Thu, 11 Feb 2010 22:02:33 +0000 (22:02 +0000)
git-svn-id: https://s.snth.net/svn/neverball/trunk@3142 78b8d119-cf0a-0410-b17c-f493084dd1d7

INSTALL
Makefile
doc/AUTHORS
doc/MANUAL
share/tilt_loop.c [new file with mode: 0644]
share/vec3.c
share/vec3.h

diff --git a/INSTALL b/INSTALL
index d630bb7..5d5248b 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -23,6 +23,11 @@ library must be installed and configured:
     BlueZ             http://www.bluez.org/
     libwiimote        http://libwiimote.sourceforge.net/
 
+To  build Neverball  with  Hillcrest Labs  Loop  support enabled,  the
+libfreespace and libusb libraries must be installed and configured:
+
+    libusb-1.0        http://www.libusb.org/wiki/Libusb1.0
+    libfreespace      http://libfreespace.hillcrestlabs.com/
 
 * FONTS
 
@@ -57,6 +62,11 @@ enabled.
         Enable Wii Remote support.   Requires additional libraries and
         system configuration.
 
+    ENABLE_TILT=loop
+
+        Enable  Hillcrest  Labs  Loop  support.   Requires  additional
+        libraries and system configuration.
+
 Under Mac OS X, build using the provided Xcode project files.
 
 For  Windows  builds,  the   MinGW  cross-compilation  environment  is
index 90d858a..d12b456 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -117,6 +117,10 @@ INTL_LIBS :=
 
 ifeq ($(ENABLE_TILT),wii)
     TILT_LIBS := -lcwiimote -lbluetooth
+else
+ifeq ($(ENABLE_TILT),loop)
+    TILT_LIBS := -lusb-1.0 -lfreespace
+endif
 endif
 
 ifeq ($(ENABLE_ODE),1)
@@ -304,8 +308,12 @@ endif
 ifeq ($(ENABLE_TILT),wii)
 BALL_OBJS += share/tilt_wii.o
 else
+ifeq ($(ENABLE_TILT),loop)
+BALL_OBJS += share/tilt_loop.o
+else
 BALL_OBJS += share/tilt_null.o
 endif
+endif
 
 ifeq ($(PLATFORM),mingw)
 BALL_OBJS += neverball.ico.o
index e891216..0e37dc0 100644 (file)
@@ -161,6 +161,9 @@ D: Developer
 D: Latvian translation
 D: Neverball maps
 
+A: rotajota
+D: Hillcrest Labs Loop support
+
 N: Alexander Schmehl
 D: Debian packaging
 
index 7eca988..fce8b40 100644 (file)
@@ -458,4 +458,22 @@ feel sluggish.  This might improve with  an IR sensor bar, but has not
 been tested.
 
 
+* HILLCREST LABS LOOP SUPPORT
+
+For  information on how  to build  the game  with Hillcrest  Labs Loop
+support on all platforms, see  instructions in the file INSTALL in the
+source archive.
+
+To use a loop  to control the game, it must be  plugged in at startup.
+If  the  loop  is  plugged  in,  it will  be  chosen  as  the  default
+controller.   Hold  the  loop  upright,  and tilt  it  left/right  and
+forward/backwards to control the  game.  Occasionally the sensors will
+get miscalibrated;  to fix  this, simply  set the loop  on a  table or
+stable surface for about 5 seconds, and the controls should be back to
+normal.
+
+When  not in game  mode, the  loop will  act like  a mouse  to control
+menus.  When the  game is playing, the left  and right buttons control
+the camera and the middle button will bring up a pause menu.
+
 Web: <http://neverball.org/>
diff --git a/share/tilt_loop.c b/share/tilt_loop.c
new file mode 100644 (file)
index 0000000..6672319
--- /dev/null
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2010 Neverball contributors
+ *
+ * NEVERBALL is  free software; you can redistribute  it and/or modify
+ * it under the  terms of the GNU General  Public License as published
+ * by the Free  Software Foundation; either version 2  of the License,
+ * or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT  ANY  WARRANTY;  without   even  the  implied  warranty  of
+ * MERCHANTABILITY or  FITNESS FOR A PARTICULAR PURPOSE.   See the GNU
+ * General Public License for more details.
+ */
+
+#include <SDL.h>
+#include <SDL_thread.h>
+#include <math.h>
+#include <stdio.h>
+
+#include "config.h"
+
+/*---------------------------------------------------------------------------*/
+
+/* FIXME: I did not copy paste this from tilt_wii.c, I swear! */
+
+/*
+ * This data structure tracks button changes, counting transitions so that
+ * none are missed if the event handling thread falls significantly behind
+ * the device IO thread.
+ */
+
+#define BUTTON_NC 0
+#define BUTTON_DN 1
+#define BUTTON_UP 2
+
+struct button_state
+{
+    unsigned char curr;
+    unsigned char last;
+    unsigned char upc;
+    unsigned char dnc;
+};
+
+static void set_button(struct button_state *B, int s)
+{
+    if ((B->curr == 0) != (s == 0))
+    {
+        if (B->curr)
+        {
+            B->upc++;
+            B->curr = 0;
+        }
+        else
+        {
+            B->dnc++;
+            B->curr = 1;
+        }
+    }
+}
+
+static int get_button(struct button_state *B)
+{
+    int ch = BUTTON_NC;
+
+    if      (B->last == 1 && B->upc > 0)
+    {
+        B->upc--;
+        B->last = 0;
+        ch = BUTTON_UP;
+    }
+    else if (B->last == 0 && B->dnc > 0)
+    {
+        B->dnc--;
+        B->last = 1;
+        ch = BUTTON_DN;
+    }
+
+    return ch;
+}
+
+/*---------------------------------------------------------------------------*/
+
+struct tilt_state
+{
+    int   status;
+    float x;
+    float z;
+    struct button_state A;
+    struct button_state B;
+    struct button_state plus;
+    struct button_state minus;
+    struct button_state home;
+    struct button_state L;
+    struct button_state R;
+    struct button_state U;
+    struct button_state D;
+};
+
+static struct tilt_state state;
+static SDL_mutex        *mutex  = NULL;
+static SDL_Thread       *thread = NULL;
+
+/*---------------------------------------------------------------------------*/
+
+#include "freespace/freespace.h"
+#include "freespace/freespace_codecs.h"
+#include "vec3.h"
+
+#define FILTER 8
+#define DAMPENING 1
+
+static int tilt_func(void *data)
+{
+    FreespaceDeviceId deviceId;
+    uint8_t buffer[FREESPACE_MAX_OUTPUT_MESSAGE_SIZE];
+    int rc;
+    float x, y, z;
+    struct freespace_DataMotionControl d;
+    struct freespace_UserFrame userFrame;
+    int running = 1;
+    int length;
+    int numIds;
+
+    float quat[4];
+    float eulerAngles[3];
+
+
+    rc = freespace_getDeviceList(&deviceId, 1, &numIds);
+    if (numIds == 0) {
+        return 1;
+    }
+
+    rc = freespace_openDevice(deviceId);
+    if (rc != FREESPACE_SUCCESS) {
+        return 1;
+    }
+
+    rc = freespace_flush(deviceId);
+    if (rc != FREESPACE_SUCCESS) {
+        return 1;
+    }
+
+    d.enableBodyMotion = 0;
+    d.enableUserPosition = 1;
+    d.inhibitPowerManager = 0;
+    d.enableMouseMovement = 1;
+    d.disableFreespace = 0;
+    rc = freespace_encodeDataMotionControl(&d, buffer, sizeof(buffer));
+    if (rc > 0) {
+        rc = freespace_send(deviceId, buffer, rc);
+        if (rc != FREESPACE_SUCCESS) {
+            return 1;
+        }
+    }
+
+
+    SDL_mutexP(mutex);
+    state.status = running;
+    SDL_mutexV(mutex);
+
+    while (mutex && running)
+    {
+        SDL_mutexP(mutex);
+        running = state.status;
+        SDL_mutexV(mutex);
+
+        rc = freespace_read(deviceId, buffer, FREESPACE_MAX_INPUT_MESSAGE_SIZE, 100, &length);
+        if (rc != FREESPACE_SUCCESS) {
+            continue;
+        }
+
+        if (length == 0) {
+            continue;
+        }
+
+        if (freespace_decodeUserFrame(buffer, length, &userFrame) == FREESPACE_SUCCESS) {
+            /* Hillcrest quaternion is rotate the world type, so make it rotate the object type by conjugating*/
+            quat[0] = userFrame.angularPosA;
+            quat[1] = -userFrame.angularPosB;
+            quat[2] = -userFrame.angularPosC;
+            quat[3] = -userFrame.angularPosD;
+            q_nrm(quat, quat);
+
+            /* This function does euler decomposition for rotate the object type (ZYX, aerospace) */
+            q_euler(eulerAngles, quat);
+
+            SDL_mutexP(mutex);
+            {
+                /* Since the game expects "rotate the world type", conjugate by negating all angles & convert to degrees
+                 * Z is yaw
+                 * Y is pitch
+                 * X is roll */
+                z = -eulerAngles[0] * 57.2957795;
+                y = -eulerAngles[1] * 57.2957795;
+                x = -eulerAngles[2] * 57.2957795;
+
+                state.x = y * DAMPENING;
+                state.z = -x * DAMPENING;
+
+                set_button(&state.home, userFrame.button3);
+                set_button(&state.U, userFrame.deltaWheel > 0);
+                set_button(&state.D, userFrame.deltaWheel < 0);
+            }
+            SDL_mutexV(mutex);
+        }
+
+    }
+
+    freespace_closeDevice(deviceId);
+    return 0;
+}
+
+void tilt_init(void)
+{
+    memset(&state, 0, sizeof (struct tilt_state));
+
+    freespace_init();
+
+    mutex  = SDL_CreateMutex();
+    thread = SDL_CreateThread(tilt_func, NULL);
+}
+
+void tilt_free(void)
+{
+    int b = 0;
+
+    if (mutex)
+    {
+        /* Get/set the status of the tilt sensor thread. */
+
+        SDL_mutexP(mutex);
+        b = state.status;
+        state.status = 0;
+        SDL_mutexV(mutex);
+
+        /* Kill the thread and destroy the mutex. */
+
+        SDL_WaitThread(thread, &b);
+        SDL_DestroyMutex(mutex);
+
+        mutex  = NULL;
+        thread = NULL;
+
+        freespace_exit();
+    }
+}
+
+int tilt_get_button(int *b, int *s)
+{
+    int ch = BUTTON_NC;
+
+    if (mutex)
+    {
+        SDL_mutexP(mutex);
+        {
+            if      ((ch = get_button(&state.A)))
+            {
+                *b = config_get_d(CONFIG_JOYSTICK_BUTTON_A);
+                *s = (ch == BUTTON_DN);
+            }
+            else if ((ch = get_button(&state.B)))
+            {
+                *b = config_get_d(CONFIG_JOYSTICK_BUTTON_B);
+                *s = (ch == BUTTON_DN);
+            }
+            else if ((ch = get_button(&state.home)))
+            {
+                *b = config_get_d(CONFIG_JOYSTICK_BUTTON_EXIT);
+                *s = (ch == BUTTON_DN);
+            }
+            else if ((ch = get_button(&state.U)))
+            {
+                *b = config_get_d(CONFIG_JOYSTICK_DPAD_U);
+                *s = (ch == BUTTON_DN);
+            }
+            else if ((ch = get_button(&state.D)))
+            {
+                *b = config_get_d(CONFIG_JOYSTICK_DPAD_D);
+                *s = (ch == BUTTON_DN);
+            }
+        }
+        SDL_mutexV(mutex);
+    }
+    return ch;
+}
+
+float tilt_get_x(void)
+{
+    float x = 0.0f;
+
+    if (mutex)
+    {
+        SDL_mutexP(mutex);
+        x = state.x;
+        SDL_mutexV(mutex);
+    }
+
+    return x;
+}
+
+float tilt_get_z(void)
+{
+    float z = 0.0f;
+
+    if (mutex)
+    {
+        SDL_mutexP(mutex);
+        z = state.z;
+        SDL_mutexV(mutex);
+    }
+
+    return z;
+}
+
+int tilt_stat(void)
+{
+    int b = 0;
+
+    if (mutex)
+    {
+        SDL_mutexP(mutex);
+        b = state.status;
+        SDL_mutexV(mutex);
+    }
+    return b;
+}
+
+/*---------------------------------------------------------------------------*/
index f52b208..fdc40f5 100644 (file)
@@ -303,4 +303,37 @@ void q_axisangle(const float *q, float *u, float *a)
     v_nrm(u, q + 1);
 }
 
+void q_nrm(float *q, const float *r)
+{
+    float d = q_len(r);
+
+    if (d == 0.0f)
+    {
+        q[0] = 1.0f;
+        q[1] = 0.0f;
+        q[2] = 0.0f;
+        q[3] = 0.0f;
+    }
+    else
+    {
+        q[0] = r[0] / d;
+        q[0] = r[1] / d;
+        q[0] = r[2] / d;
+        q[0] = r[3] / d;
+    }
+}
+
+void q_euler(float *v, const float *q)
+{
+    float m11 = (2 * q[0] * q[0]) + (2 * q[1] * q[1]) - 1;
+    float m12 = (2 * q[1] * q[2]) + (2 * q[0] * q[3]);
+    float m13 = (2 * q[1] * q[3]) - (2 * q[0] * q[2]);
+    float m23 = (2 * q[2] * q[3]) + (2 * q[0] * q[1]);
+    float m33 = (2 * q[0] * q[0]) + (2 * q[3] * q[3]) - 1;
+
+    v[0] = fatan2f(m12, m11);
+    v[1] = fasinf(-m13);
+    v[2] = fatan2f(m23, m33);
+}
+
 /*---------------------------------------------------------------------------*/
index 1abf56d..4d8e2cf 100644 (file)
@@ -107,6 +107,9 @@ void   m_view(float *, const float *,
 
 /*---------------------------------------------------------------------------*/
 
+#define q_dot(q, r) ((q)[0] * (r)[0] + v_dot((q) + 1, (r) + 1))
+#define q_len(q)    fsqrtf(q_dot((q), (q)))
+
 #define q_cpy(q, p) do { \
     (q)[0] = (p)[0];     \
     (q)[1] = (p)[1];     \
@@ -115,5 +118,7 @@ void   m_view(float *, const float *,
 } while (0)
 
 void q_axisangle(const float *q, float *u, float *a);
+void q_nrm(float *q, const float *r);
+void q_euler(float *v, const float *q);
 
 #endif