Merge: gamnit VR: steroscopic view and head tracking on Android
authorJean Privat <jean@pryen.org>
Thu, 21 Jan 2016 01:19:36 +0000 (20:19 -0500)
committerJean Privat <jean@pryen.org>
Thu, 21 Jan 2016 01:19:36 +0000 (20:19 -0500)
This PR adds support for VR to the _gamnit depth_ framework. By importing `gamnit::vr`, all drawings are duplicated for a stereoscopic view and the `world_camera` orientation is updated from head tracking data.

VR is applied to the `model_viewer` app for testing only. The UI should still be updated for a nicer VR experience.

This is done by simple refinement, which allows for easy (but dirty) conversion to VR using `nitc ... -m lib/gamnit/depth/vr.nit`. However this does not allow to easily switch between VR and a classic view. This could be improved as needed in the future by using a subclass of `EulerCamera` and a few conditions in `frame_core_draw`.

Pull-Request: #1923
Reviewed-by: Jean Privat <jean@pryen.org>

contrib/model_viewer/Makefile
contrib/model_viewer/libs/.gitignore [new file with mode: 0644]
lib/android/cardboard.nit
lib/gamnit/depth/cardboard.nit [new file with mode: 0644]
lib/gamnit/depth/depth.nit
lib/gamnit/depth/stereoscopic_view.nit [new file with mode: 0644]
lib/gamnit/depth/vr.nit [new file with mode: 0644]
lib/gamnit/flat.nit

index ebd0143..4557a44 100644 (file)
@@ -29,3 +29,10 @@ res/drawable-hdpi/icon.png: art/icon.png
        convert -resize 96x96   art/icon.png res/drawable-xhdpi/icon.png
        convert -resize 144x144 art/icon.png res/drawable-xxhdpi/icon.png
        convert -resize 192x192 art/icon.png res/drawable-xxxhdpi/icon.png
+
+bin/model_viewer_vr.apk: $(shell ${NITLS} -M src/model_viewer.nit android) ${NITC} res/drawable-hdpi/icon.png libs/cardboard.jar
+       ${NITC} src/model_viewer.nit -m android -m ../../lib/gamnit/depth/vr.nit -o $@
+
+libs/cardboard.jar:
+       curl --progress-bar -o libs/cardboard.jar \
+       https://raw.githubusercontent.com/googlesamples/cardboard-java/master/CardboardSample/libs/cardboard.jar
diff --git a/contrib/model_viewer/libs/.gitignore b/contrib/model_viewer/libs/.gitignore
new file mode 100644 (file)
index 0000000..72e8ffc
--- /dev/null
@@ -0,0 +1 @@
+*
index b599a37..15eff41 100644 (file)
@@ -51,11 +51,6 @@ extern class NativeHeadTracker in "Java" `{ com.google.vrtoolkit.cardboard.senso
        # Stop tracking head movement
        fun stop_tracking in "Java" `{ self.stopTracking(); `}
 
-       # Apply correction to the gyroscope values
-       fun gyro_bias=(matrix: JavaFloatArray) in "Java" `{
-               self.setGyroBias(matrix);
-       `}
-
        # Enable finer analysis using the neck as center of movement
        fun neck_model_enabled=(value: Bool) in "Java" `{
                self.setNeckModelEnabled(value);
diff --git a/lib/gamnit/depth/cardboard.nit b/lib/gamnit/depth/cardboard.nit
new file mode 100644 (file)
index 0000000..ccecf15
--- /dev/null
@@ -0,0 +1,108 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Update the orientation of `world_camera` at each frame using the head position given by `android::cardboard`
+#
+# This module is Android specific.
+module cardboard
+
+import ::android::cardboard
+
+import depth
+intrude import cameras
+
+redef class EulerCamera
+       # Do not use `yaw` and `pitch`, the value will instead originate from the Cardboard API
+       redef var rotation_matrix = new Matrix.identity(4)
+
+       # Get the angle value from the `rotation_matrix`
+       redef fun pitch
+       do
+               var a = rotation_matrix[0, 1]
+               var b = rotation_matrix[1, 1]
+               return -atan2(a, b)
+       end
+
+       # Get the angle value from the `rotation_matrix`
+       redef fun yaw
+       do
+               var a = rotation_matrix[2, 0]
+               var b = rotation_matrix[2, 2]
+               return -atan2(a, b)
+       end
+end
+
+redef class App
+
+       # Cardboard's head tacker instance
+       private var head_tracker: nullable NativeHeadTracker = null
+
+       # Rotation matrix read from `head_tracker`, reusing the same structure as a buffer
+       private var java_rotation_matrix = new JavaFloatArray(16) is lazy
+
+       # Initialize and set `head_tracker`
+       fun initialize_head_tracker
+       do
+               # Initialize the Cardboard head orientation tracker service
+               var head_tracker = new NativeHeadTracker(app.native_activity)
+               head_tracker.neck_model_enabled = true
+               head_tracker.start_tracking
+               self.head_tracker = head_tracker
+
+               # Set a wide field of view
+               world_camera.field_of_view_y = 1.0
+       end
+
+       # Read the rotation matrix from Cardboard and update `world_camera`
+       private fun update_from_head_tracker
+       do
+               var head_tracker = head_tracker
+               if head_tracker == null then return
+
+               head_tracker.last_head_view(java_rotation_matrix, 0)
+
+               # Copy values from the Java array to our matrix
+               for y in [0..4[ do
+                       for x in [0..4[ do
+                               world_camera.rotation_matrix[y, x] = java_rotation_matrix[y*4+x]
+                       end
+               end
+       end
+
+       redef fun on_create
+       do
+               super
+               initialize_head_tracker
+       end
+
+       redef fun update(dt)
+       do
+               super
+               update_from_head_tracker
+       end
+
+       redef fun pause
+       do
+               super
+               var tracker = head_tracker
+               if tracker != null then tracker.stop_tracking
+       end
+
+       redef fun resume
+       do
+               super
+               var tracker = head_tracker
+               if tracker != null then tracker.start_tracking
+       end
+end
index e17b5af..c2328c6 100644 (file)
@@ -40,11 +40,11 @@ redef class App
                assert gamnit_error == null else print_error gamnit_error
        end
 
-       # Draw all element in `actors`
-       redef fun frame_core_draw(display)
-       do
-               super
+       redef fun frame_core_draw(display) do frame_core_depth display
 
+       # Draw all elements of `actors` and then call `frame_core_flat`
+       protected fun frame_core_depth(display: GamnitDisplay)
+       do
                # Update cameras on both our programs
                versatile_program.use
                versatile_program.mvp.uniform world_camera.mvp_matrix
@@ -57,5 +57,7 @@ redef class App
                                leaf.material.draw(actor, leaf)
                        end
                end
+
+               frame_core_flat display
        end
 end
diff --git a/lib/gamnit/depth/stereoscopic_view.nit b/lib/gamnit/depth/stereoscopic_view.nit
new file mode 100644 (file)
index 0000000..2857e7d
--- /dev/null
@@ -0,0 +1,88 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Refine `EulerCamera` and `App::frame_core_draw` to get a stereoscopic view
+module stereoscopic_view
+
+import depth
+intrude import cameras
+
+redef class EulerCamera
+       redef var mvp_matrix = new Matrix.identity(4)
+
+       # Half of the distance between the eyes
+       var eye_separation: Float = 0.03125
+
+       # MVP matrix for the left eye
+       fun mvp_matrix_left: Matrix do return mvp_matrix_eye(eye_separation)
+
+       # MVP matrix for the right eye
+       fun mvp_matrix_right: Matrix do return mvp_matrix_eye(-eye_separation)
+
+       # Get an MVP matrix for an eye at `diff` world unit from the center
+       private fun mvp_matrix_eye(diff: Float): Matrix
+       do
+               var view = new Matrix.identity(4)
+
+               # Translate the world away from the camera
+               view.translate(-position.x/2.0, -position.y/2.0, -position.z/2.0)
+
+               # Rotate the camera, first by looking left or right, then up or down
+               view = view * rotation_matrix
+
+               # Apply eye transformation
+               var translation = new Matrix.identity(4)
+               translation.translate(diff, 0.0, 0.0)
+               view = view * translation
+
+               # Use a projection matrix with a depth
+               var projection = new Matrix.perspective(pi*field_of_view_y/2.0,
+                       display.aspect_ratio, near, far)
+
+               return view * projection
+       end
+end
+
+redef class GamnitDisplay
+
+       # With stereoscopic view, the aspect ratio (in each eye) is half of the screen
+       redef fun aspect_ratio do return super / 2.0
+end
+
+redef class App
+       redef fun frame_core_draw(display) do frame_core_stereoscopic display
+
+       # Split the screen in two, and call `frame_core_depth` for each eyes
+       protected fun frame_core_stereoscopic(display: GamnitDisplay)
+       do
+               var half_width = display.width / 2
+
+               # Left eye
+               glViewport(0, 0, half_width, display.height)
+               world_camera.mvp_matrix = world_camera.mvp_matrix_left
+               frame_core_depth display
+
+               # Right eye
+               glViewport(half_width, 0, half_width, display.height)
+               world_camera.mvp_matrix = world_camera.mvp_matrix_right
+               frame_core_depth display
+
+               # We reset the viewport for selection
+               glViewport(0, 0, display.width, display.height)
+
+               # Check for errors
+               var gl_error = glGetError
+               assert gl_error == gl_NO_ERROR else print gl_error
+       end
+end
diff --git a/lib/gamnit/depth/vr.nit b/lib/gamnit/depth/vr.nit
new file mode 100644 (file)
index 0000000..1c3a845
--- /dev/null
@@ -0,0 +1,19 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# VR support for gamnit depth, for Android only
+module vr
+
+import cardboard
+import stereoscopic_view
index eaa5ba8..90cefca 100644 (file)
@@ -179,8 +179,11 @@ redef class App
                assert gl_error == gl_NO_ERROR else print gl_error
        end
 
+       # Draw the whole screen, all `glDraw...` calls should be executed here
+       protected fun frame_core_draw(display: GamnitDisplay) do frame_core_flat display
+
        # Draw sprites in `sprites` and `ui_sprites`
-       protected fun frame_core_draw(display: GamnitDisplay)
+       protected fun frame_core_flat(display: GamnitDisplay)
        do
                simple_2d_program.use