diff --git a/.gitignore b/.gitignore index 4f08c5f41..972670fd8 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,20 @@ packets_log.txt history.dat README.dependencies xx + +android/android-ndk* +android/android-sdk* +android/assets +android/bin +android/obj +android/libs +android-* +*.apk + +lib/curl +lib/freetype +lib/ifaddrs +lib/libogg +lib/libvorbis +lib/openal +lib/openssl diff --git a/android/Android.mk b/android/Android.mk new file mode 100644 index 000000000..08728da5f --- /dev/null +++ b/android/Android.mk @@ -0,0 +1,174 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + + +# OpenAL +LOCAL_MODULE := openal +LOCAL_SRC_FILES := obj/openal/libopenal.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# OGG +LOCAL_MODULE := ogg +LOCAL_SRC_FILES := obj/libogg/src/.libs/libogg.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# Vorbis +LOCAL_MODULE := vorbis +LOCAL_SRC_FILES := obj/libvorbis/lib/.libs/libvorbis.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# Vorbisfile +LOCAL_MODULE := vorbisfile +LOCAL_SRC_FILES := obj/libvorbis/lib/.libs/libvorbisfile.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# CURL +LOCAL_MODULE := curl +LOCAL_SRC_FILES := obj/curl/lib/.libs/libcurl.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# libcrypto +LOCAL_MODULE := libcrypto +LOCAL_SRC_FILES := obj/openssl/libcrypto.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# libssl +LOCAL_MODULE := libssl +LOCAL_SRC_FILES := obj/openssl/libssl.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# JPEG +LOCAL_MODULE := jpeglib +LOCAL_SRC_FILES := obj/jpeglib/libjpeglib.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# Freetype +LOCAL_MODULE := freetype +LOCAL_SRC_FILES := obj/freetype/objs/.libs/libfreetype.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# zlib +LOCAL_MODULE := zlib +LOCAL_SRC_FILES := obj/zlib/libz.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# PNG +LOCAL_MODULE := png +LOCAL_SRC_FILES := obj/libpng/libpng.a +include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# ifaddrs +LOCAL_MODULE := ifaddrs +LOCAL_PATH := . +LOCAL_SRC_FILES := ../lib/ifaddrs/ifaddrs.c +LOCAL_CFLAGS := -I../lib/ifaddrs +include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# AngelScript +LOCAL_MODULE := angelscript +LOCAL_PATH := . +LOCAL_CPP_FEATURES += rtti exceptions +LOCAL_SRC_FILES := $(wildcard ../lib/angelscript/source/*.S) \ + $(wildcard ../lib/angelscript/source/*.cpp) +LOCAL_CFLAGS := -I../lib/angelscript/source/ +include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# ENET +LOCAL_MODULE := enet +LOCAL_PATH := . +LOCAL_CPP_FEATURES += rtti +LOCAL_SRC_FILES := $(wildcard ../lib/enet/*.c) +LOCAL_CFLAGS := -I../lib/enet/include/ -DHAS_SOCKLEN_T +include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# Bullet +LOCAL_MODULE := bullet +LOCAL_PATH := . +LOCAL_CPP_FEATURES += rtti +LOCAL_SRC_FILES := $(wildcard ../lib/bullet/src/*/*.cpp) \ + $(wildcard ../lib/bullet/src/*/*/*.cpp) +LOCAL_CFLAGS := -I../lib/bullet/src/ +include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# Irrlicht +LOCAL_MODULE := irrlicht +LOCAL_PATH := . +LOCAL_CPP_FEATURES += rtti +LOCAL_SRC_FILES := $(wildcard ../lib/irrlicht/source/Irrlicht/*.cpp) \ + $(wildcard ../lib/irrlicht/source/Irrlicht/Android/*.cpp) +LOCAL_CFLAGS := -I../lib/irrlicht/source/Irrlicht/ \ + -I../lib/irrlicht/include/ \ + -Iobj/jpeglib/ \ + -Iobj/libpng/ \ + -Iobj/zlib/ \ + -I$(call my-dir)/../../sources/android/native_app_glue + -std=gnu++0x +LOCAL_STATIC_LIBRARIES := jpeglib png zlib +include $(BUILD_STATIC_LIBRARY) +include $(CLEAR_VARS) + + +# STK +LOCAL_MODULE := main +LOCAL_PATH := . +LOCAL_CPP_FEATURES += rtti exceptions +LOCAL_SRC_FILES := $(wildcard ../src/*.cpp) \ + $(wildcard ../src/*/*.cpp) \ + $(wildcard ../src/*/*/*.cpp) +LOCAL_LDLIBS := -llog -landroid -lEGL -lGLESv3 -lOpenSLES +LOCAL_CFLAGS := -I../lib/angelscript/include \ + -I../lib/bullet/src \ + -I../lib/enet/include \ + -I../lib/ifaddrs \ + -I../lib/irrlicht/include \ + -I../lib/irrlicht/source/Irrlicht \ + -I../src \ + -Iobj/curl/include \ + -Iobj/freetype/include \ + -Iobj/libogg/include \ + -Iobj/libvorbis/include \ + -Iobj/openal/include \ + -I$(call my-dir)/../../sources/android/native_app_glue \ + -DUSE_GLES2 \ + -DHAVE_OGGVORBIS \ + -DNDEBUG \ + -std=gnu++0x + +LOCAL_STATIC_LIBRARIES := irrlicht bullet enet freetype ifaddrs angelscript \ + vorbisfile vorbis ogg openal curl libssl libcrypto \ + gnustl_static android_native_app_glue + +include $(BUILD_SHARED_LIBRARY) +include $(CLEAR_VARS) + +$(call import-module,android/native_app_glue) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml new file mode 100644 index 000000000..1a6a1f61d --- /dev/null +++ b/android/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/README.ANDROID b/android/README.ANDROID new file mode 100644 index 000000000..85903d41d --- /dev/null +++ b/android/README.ANDROID @@ -0,0 +1,134 @@ +================================================================================ + + SUPERTUXKART + +================================================================================ + + + +-------------------------------------------------------------------------------- + SYSTEM REQUIREMENTS +-------------------------------------------------------------------------------- + +To run SuperTuxKart on Android, you need a device that meets following +requirements: + +- Android 4.4 or later +- Processor compatible with armv7 or x86 +- GPU that supports OpenGL ES 3.0 +- 1 GB RAM (STK uses ~150 MB in minimal configuration) +- 300 MB of free space on internal storage +- Touch screen or external keyboard + + + +-------------------------------------------------------------------------------- + COMPILATION +-------------------------------------------------------------------------------- + +The build scripts are designed to run under linux. They may work under cygwin +after some tweaks, but atm. only linux is supported. + +Dependencies list (may be incomplete): + + autoconf, automake, make, python, ant, imagemagick, cmake, vorbis-tools + +Additionally some dependencies for optimize_data script: + + advancecomp, libjpeg-progs, optipng + +Before compilation you must download the package with dependencies from: +https://github.com/supertuxkart/dependencies +and extract it to stk-code/lib. It contains sources of libraries that are used +in STK, but are not availiable in stk-code repository (curl, freetype, openal). + +You need also Android SDK for android-19 platform (the API for Android 4.4) and +Android NDK (versions r12b and r13b have been tested). + +You need to create proper "android-sdk" and "android-ndk" symlinks in the +directory with Android project, so that the compilation script will have access +to the SDK and NDK. + +These paths can be also set in SDK_PATH and NDK_PATH environmental variables. + +Before running the compilation, run the generate_assets script, so that +selected assets will be copied to "assets" directory, and then included in the +apk file. + +You can select different karts and tracks by setting KARTS and TRACKS variables +in the generate_assets.sh script at the beginning of file. + +When you are creating the assets directory manually, note that the +directories.txt file is urgently needed and it is used by the application for +extracting assets. + +If the assets directory is already prepared, you can run "./make.sh" command to +build the project and create an apk file. Note that all arguments are passed to +the make command, so that you can run "./make.sh -j5" for multi-threaded build. + +If you want to prepare a package for particular architecture, you can choose it +by setting the COMPILE_ARCH environmental variable. At this stage, supported +architectures are "armv7", "x86" and "aarch64". The default is "armv7". + +Basically if all dependencies are installed in the system, it should be enough +to just run: + + export SDK_PATH=/path/to/your/android/sdk + export NDK_PATH=/path/to/your/android/ndk + ./generate_assets.sh + ./make.sh -j5 + + + +-------------------------------------------------------------------------------- + KNOWN ISSUES +-------------------------------------------------------------------------------- + +1. At this stage only shader-based (OpenGL ES 3.0) pipeline works. The fixed + pipeline (GLES 2.0) could work (it works under linux), but it doesn't look + good and is generally broken. It means that it's not possible to run STK on + Android 4.2 or older. It is technically possible to do - check GLES context + version, load OpenGL functions dynamically using EGL, and if they are not + loaded properly, then fallback to GLES 2.0. But these devices may be too + slow to run STK anyway. + +2. It never ocurred for me, but it's possible that EGL context is lost in some + cases. SuperTuxKart is not designed to re-create all textures at any moment, + so this is a "Wontfix", at least for now. + +3. Some bright tracks (Farm, Gran Paradiso) seem to be a bit darker in GLES + renderer than in original OpenGL 3.x renderer. It can be easily hacked by + adding few lines to object pass shader, but we should rather try to find the + real reason. + +4. Explosion effect has poor performance and causes fps drop for a while. + Though it can be easily tweaked, so that less particles per second is + generated. + +5. Touch steering needs nice button icons. + +6. We use "exit(0)" at the end of main function. We shouldn't do it and we + should just return from the main function. But STK uses some global + variables and their values are remembered when the game is restarted. We + should properly clear them or re-initialize on startup. Using the "exit(0)" + is not-that-bad workaround, but it may cause a crash on exit sometimes. + It seems to affect only Android 5.0. More information about the crash: + https://code.google.com/p/android/issues/detail?id=160824 + +7. STK crashes on Qualcomm with Adreno 305 when trying to draw menu interface. + Backtrace shows glDrawElements function, and internally crashed in vbo + allocation. + +8. STK crashes on startup on some devices when aarch64 build is made using + Android r13 NDK. The r13 version has rather big modifications (it uses clang + instead of gcc by default). This is probably a bug in NDK/compiler/OS, but + for this reason using NDK r12 for 64-bit arm compilation is preferred. + +9. Angelscript doesn't have full support for aarch64 builds, so that scripting + won't work on this platform. + +10. Steering with accelerometer is not available yet. It needs some work to do + it properly because tablets have different screen orientation than phones, + so that they receive events from different axies during rotating the device. + As far as I see it's not possible to read default screen orientation using + NDK functions. diff --git a/android/build.xml b/android/build.xml new file mode 100644 index 000000000..54c3d58b4 --- /dev/null +++ b/android/build.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/generate_assets.sh b/android/generate_assets.sh new file mode 100755 index 000000000..2fdec9214 --- /dev/null +++ b/android/generate_assets.sh @@ -0,0 +1,240 @@ +#!/bin/sh +# +# (C) 2016-2017 Dawid Gan, under the GPLv3 +# +# A script that generates data files for Android apk + + +# Below are simple configuration variables +# It's allowed to set "all" for KARTS and TRACKS if it's intended to create +# package with full data. +# The karts and tracks directories shouldn't exist in ASSETS_DIRS variable +# because they are handled separately +# The TEXTURE_SIZE and SOUND_QUALITY take effect only if DECREASE_QUALITY has +# value greater than 0 +# The script needs imagemagick and ogg utils installed to use DECREASE_QUALITY +# feature + +################################################################################ + +export KARTS="tux nolok xue" +export TRACKS="battleisland cornfield_crossing featunlocked gplose gpwin \ + hacienda introcutscene introcutscene2 lighthouse olivermath \ + overworld snowmountain snowtuxpeak soccer_field tutorial" + +export ASSETS_PATHS="../data \ + ../../stk-assets \ + ../../supertuxkart-assets" + +export ASSETS_DIRS="library models music sfx textures" + +export TEXTURE_SIZE=256 +export SOUND_QUALITY=64 + +export RUN_OPTIMIZE_SCRIPT=0 +export DECREASE_QUALITY=1 + +################################################################################ + + +cd "`dirname "$0"`" + +# Find assets path +for ASSETS_PATH in $ASSETS_PATHS; do + if [ -d $ASSETS_PATH ] && [ `ls $ASSETS_PATH | grep -c tracks` -gt 0 ]; then + echo "Assets found in $ASSETS_PATH" + ASSETS_PATH_FOUND=1 + break + fi +done + +if [ -z $ASSETS_PATH_FOUND ]; then + echo "Couldn't find assets path" + exit 1 +fi + +if [ ! -d "../data" ]; then + echo "Couldn't find data directory" + exit 1 +fi + + +# Clear previous assets directory +echo "Clear previous assets directory" +rm -rf assets + + +# Copy all assets +echo "Copy all assets" + +mkdir -p assets/data + +for DIR in `ls $ASSETS_PATH`; do + CAN_BE_COPIED=0 + + for ASSETS_DIR in $ASSETS_DIRS; do + if [ $DIR = $ASSETS_DIR ]; then + CAN_BE_COPIED=1 + break + fi + done; + + # Don't copy karts and tracks. It will be handled later + BLACKLIST_ASSETS="karts tracks" + for ASSETS_DIR in $BLACKLIST_ASSETS; do + if [ $DIR = $ASSETS_DIR ]; then + CAN_BE_COPIED=0 + break + fi + done; + + if [ $CAN_BE_COPIED -gt 0 ]; then + cp -a "$ASSETS_PATH/$DIR" assets/data/ + fi +done; + + +# Copy selected tracks +echo "Copy selected tracks" + +mkdir -p assets/data/tracks + +for DIR in `ls $ASSETS_PATH/tracks`; do + CAN_BE_COPIED=0 + + if [ "$TRACKS" != "all" ]; then + for TRACK in $TRACKS; do + if [ $DIR = $TRACK ]; then + CAN_BE_COPIED=1 + break + fi + done; + else + CAN_BE_COPIED=1 + fi + + if [ $CAN_BE_COPIED -gt 0 ]; then + cp -a "$ASSETS_PATH/tracks/$DIR" assets/data/tracks/ + fi +done + + +# Copy selected karts +echo "Copy selected karts" + +mkdir -p assets/data/karts + +for DIR in `ls $ASSETS_PATH/karts`; do + CAN_BE_COPIED=0 + + if [ "$KARTS" != "all" ]; then + for KART in $KARTS; do + if [ $DIR = $KART ]; then + CAN_BE_COPIED=1 + break + fi + done; + else + CAN_BE_COPIED=1 + fi + + if [ $CAN_BE_COPIED -gt 0 ]; then + cp -a "$ASSETS_PATH/karts/$DIR" assets/data/karts/ + fi +done + + +# Decrease assets quality in order to save some disk space and RAM +echo "Decrease assets quality" + +convert_image() +{ + if [ -z "$1" ]; then + echo "No file to convert" + return + fi + + FILE="$1" + + W=`identify -format "%[fx:w]" "$FILE"` + H=`identify -format "%[fx:h]" "$FILE"` + + if [ -z $W ] || [ -z $H ]; then + echo "Couldn't convert $FILE file" + return + fi + + if [ $W -le $TEXTURE_SIZE ] && [ $H -le $TEXTURE_SIZE ]; then + return + fi + + if [ $W -gt $H ]; then + SCALED_W=$TEXTURE_SIZE + SCALED_H=$(($TEXTURE_SIZE * $H / $W)) + else + SCALED_W=$(($TEXTURE_SIZE * $W / $H)) + SCALED_H=$TEXTURE_SIZE + fi + + convert -scale $SCALED_WE\x$SCALED_H "$FILE" "$FILE" +} + +convert_sound() +{ + if [ -z "$1" ]; then + echo "No file to convert" + return + fi + + FILE="$1" + + oggdec "$FILE" -o tmp.wav + + if [ -s tmp.wav ]; then + oggenc --downmix -b $SOUND_QUALITY tmp.wav -o tmp.ogg + fi + + if [ -s tmp.ogg ]; then + SIZE_OLD=`du -k "$FILE" | cut -f1` + SIZE_NEW=`du -k "tmp.ogg" | cut -f1` + + if [ $SIZE_NEW -lt $SIZE_OLD ]; then + mv tmp.ogg "$FILE" + fi + fi + + rm -f tmp.wav tmp.ogg +} + +if [ $DECREASE_QUALITY -gt 0 ]; then + find assets/data -iname "*.png" | while read f; do convert_image "$f"; done + find assets/data -iname "*.jpg" | while read f; do convert_image "$f"; done + find assets/data -iname "*.ogg" | while read f; do convert_sound "$f"; done +fi + + +# Copy data directory +echo "Copy data directory" +cp -a ../data/* assets/data/ + + +# Run optimize_data.sh script +if [ $RUN_OPTIMIZE_SCRIPT -gt 0 ]; then + echo "Run optimize_data.sh script" + sh -c 'cd assets/data; ../../../data/optimize_data.sh' +fi + + +# Generate directories list +echo "Generate directories list" +find assets/* -type d > assets/directories.txt +sed -i s/'.\/assets\/'// assets/directories.txt +sed -i s/'assets\/'// assets/directories.txt + + +# It will be probably ignored by ant, but create it anyway... +touch assets/.nomedia + + +echo "Done." +exit 0 diff --git a/android/make.sh b/android/make.sh new file mode 100755 index 000000000..ee7971e0e --- /dev/null +++ b/android/make.sh @@ -0,0 +1,288 @@ +#!/bin/sh +# +# (C) 2016-2017 Dawid Gan, under the GPLv3 +# +# A script that creates the apk build + + +export DIRNAME=$(realpath "$(dirname "$0")") + +export NDK_PATH_DEFAULT="$DIRNAME/android-ndk" +export SDK_PATH_DEFAULT="$DIRNAME/android-sdk" + +export NDK_TOOLCHAIN_PATH="$DIRNAME/obj/bin" +export NDK_BUILD_SCRIPT="$DIRNAME/Android.mk" +export PATH="$DIRNAME/obj/bin:$PATH" +export CROSS_SYSROOT="$DIRNAME/obj/sysroot" + +#export NDK_CCACHE=ccache +export NDK_CPPFLAGS="-O3 -g" + +export NDK_ABI_ARMV7=armeabi-v7a +export ARCH_ARMV7=arm +export HOST_ARMV7=arm-linux-androideabi +export NDK_PLATFORM_ARMV7=android-19 + +export NDK_ABI_X86=x86 +export ARCH_X86=x86 +export HOST_X86=i686-linux-android +export NDK_PLATFORM_X86=android-19 + +export NDK_ABI_AARCH64=arm64-v8a +export ARCH_AARCH64=arm64 +export HOST_AARCH64=aarch64-linux-android +export NDK_PLATFORM_AARCH64=android-21 + + +# A helper function that checks if error ocurred +check_error() +{ + if [ $? -gt 0 ]; then + echo "Error ocurred." + exit + fi +} + +# Handle clean command +if [ ! -z "$1" ] && [ "$1" = "clean" ]; then + rm -rf bin + rm -rf libs + rm -rf obj + exit +fi + +# Check if compilation for different platform has been started before +if [ -f "$DIRNAME/obj/compile_arch" ]; then + PROJECT_ARCH=$(cat "$DIRNAME/obj/compile_arch") + + if [ -z "$COMPILE_ARCH" ]; then + COMPILE_ARCH="$PROJECT_ARCH" + elif [ "$PROJECT_ARCH" != "$COMPILE_ARCH" ]; then + echo "Error: Compilation for different platform has been already made." + echo "Run './make.sh clean' first or set COMPILE_ARCH variable" \ + "to '$PROJECT_ARCH.'" + exit + fi +fi + +if [ -z "$COMPILE_ARCH" ]; then + COMPILE_ARCH="armv7" +fi + +# Update variables for selected architecture +if [ "$COMPILE_ARCH" = "armv7" ]; then + export NDK_PLATFORM=$NDK_PLATFORM_ARMV7 + export NDK_ABI=$NDK_ABI_ARMV7 + export ARCH=$ARCH_ARMV7 + export HOST=$HOST_ARMV7 +elif [ "$COMPILE_ARCH" = "x86" ]; then + export NDK_PLATFORM=$NDK_PLATFORM_X86 + export NDK_ABI=$NDK_ABI_X86 + export ARCH=$ARCH_X86 + export HOST=$HOST_X86 +elif [ "$COMPILE_ARCH" = "aarch64" ]; then + export NDK_PLATFORM=$NDK_PLATFORM_AARCH64 + export NDK_ABI=$NDK_ABI_AARCH64 + export ARCH=$ARCH_AARCH64 + export HOST=$HOST_AARCH64 +else + echo "Unknow COMPILE_ARCH: $COMPILE_ARCH. Possible values are: " \ + "armv7, aarch64, x86" + exit +fi + +# Check if we have access to the Android NDK and SDK +if [ -z "$NDK_PATH" ]; then + export NDK_PATH="$NDK_PATH_DEFAULT" +fi + +if [ -z "$SDK_PATH" ]; then + export SDK_PATH="$SDK_PATH_DEFAULT" +fi + +NDK_PATH=$(realpath "$NDK_PATH") +SDK_PATH=$(realpath "$SDK_PATH") + +if [ ! -d "$NDK_PATH" ]; then + echo "Error: Couldn't find $NDK_PATH directory. Please create a symlink" \ + "to your Android NDK installation in the $NDK_PATH_DEFAULT or set" \ + "proper path in the NDK_PATH variable" + exit +fi + +if [ ! -d "$SDK_PATH" ]; then + echo "Error: Couldn't find $SDK_PATH directory. Please create a symlink" \ + "to your Android SDK installation in the $SDK_PATH_DEFAULT or set" \ + "proper path in the SDK_PATH variable" + exit +fi + +# Standalone toolchain +if [ ! -f "$DIRNAME/obj/make_standalone_toolchain.stamp" ]; then + echo "Creating standalone toolchain" + rm -rf "$DIRNAME/obj" + ${NDK_PATH}/build/tools/make-standalone-toolchain.sh \ + --platform=$NDK_PLATFORM \ + --install-dir="$DIRNAME/obj/" \ + --arch=$ARCH + check_error + touch "$DIRNAME/obj/make_standalone_toolchain.stamp" + echo $COMPILE_ARCH > "$DIRNAME/obj/compile_arch" +fi + +# Freetype +if [ ! -f "$DIRNAME/obj/freetype.stamp" ]; then + echo "Compiling freetype" + mkdir -p "$DIRNAME/obj/freetype" + cp -a -f "$DIRNAME/../lib/freetype/"* "$DIRNAME/obj/freetype" + + cd "$DIRNAME/obj/freetype" + ./configure --host=$HOST \ + --without-zlib \ + --without-png \ + --without-harfbuzz && + make $@ + check_error + touch "$DIRNAME/obj/freetype.stamp" +fi + +# Zlib +if [ ! -f "$DIRNAME/obj/zlib.stamp" ]; then + echo "Compiling zlib" + mkdir -p "$DIRNAME/obj/zlib" + cp -a -f "$DIRNAME/../lib/zlib/"* "$DIRNAME/obj/zlib" + + cd "$DIRNAME/obj/zlib" + cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \ + -DHOST=$HOST && + make $@ + check_error + touch "$DIRNAME/obj/zlib.stamp" +fi + +# Libpng +if [ ! -f "$DIRNAME/obj/libpng.stamp" ]; then + echo "Compiling libpng" + mkdir -p "$DIRNAME/obj/libpng" + mkdir -p "$DIRNAME/obj/libpng/lib" + cp -a -f "$DIRNAME/../lib/libpng/"* "$DIRNAME/obj/libpng" + + cd "$DIRNAME/obj/libpng" + cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \ + -DHOST=$HOST \ + -DZLIB_LIBRARY="$DIRNAME/obj/zlib/libz.a" \ + -DZLIB_INCLUDE_DIR="$DIRNAME/obj/zlib/" \ + -DPNG_TESTS=0 && + make $@ + check_error + touch "$DIRNAME/obj/libpng.stamp" +fi + +# Openal +if [ ! -f "$DIRNAME/obj/openal.stamp" ]; then + echo "Compiling openal" + mkdir -p "$DIRNAME/obj/openal" + cp -a -f "$DIRNAME/../lib/openal/"* "$DIRNAME/obj/openal" + + cd "$DIRNAME/obj/openal" + cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \ + -DHOST=$HOST \ + -DALSOFT_UTILS=0 \ + -DALSOFT_EXAMPLES=0 \ + -DALSOFT_TESTS=0 \ + -DLIBTYPE=STATIC && + make $@ + check_error + touch "$DIRNAME/obj/openal.stamp" +fi + +# OpenSSL +if [ ! -f "$DIRNAME/obj/openssl.stamp" ]; then + echo "Compiling openssl" + mkdir -p "$DIRNAME/obj/openssl" + cp -a -f "$DIRNAME/../lib/openssl/"* "$DIRNAME/obj/openssl" + + cd "$DIRNAME/obj/openssl" + ./Configure android --cross-compile-prefix="$HOST-" + make $@ + check_error + touch "$DIRNAME/obj/openssl.stamp" +fi + +# Curl +if [ ! -f "$DIRNAME/obj/curl.stamp" ]; then + echo "Compiling curl" + mkdir -p "$DIRNAME/obj/curl" + cp -a -f "$DIRNAME/../lib/curl/"* "$DIRNAME/obj/curl" + + cd "$DIRNAME/obj/curl" + CPPFLAGS="-I$DIRNAME/obj/openssl/include $CPPFLAGS" \ + LDFLAGS="-L$DIRNAME/obj/openssl/ $LDFLAGS" \ + ./configure --host=$HOST \ + --with-ssl \ + --disable-shared \ + --enable-static \ + --enable-threaded-resolver && + make $@ + check_error + touch "$DIRNAME/obj/curl.stamp" +fi + +# Jpeglib +if [ ! -f "$DIRNAME/obj/jpeglib.stamp" ]; then + echo "Compiling jpeglib" + mkdir -p "$DIRNAME/obj/jpeglib" + cp -a -f "$DIRNAME/../lib/jpeglib/"* "$DIRNAME/obj/jpeglib" + + cd "$DIRNAME/obj/jpeglib" + cmake . -DCMAKE_TOOLCHAIN_FILE=../../../cmake/Toolchain-android.cmake \ + -DHOST=$HOST && + make $@ + check_error + touch "$DIRNAME/obj/jpeglib.stamp" +fi + +# Libogg +if [ ! -f "$DIRNAME/obj/libogg.stamp" ]; then + echo "Compiling libogg" + mkdir -p "$DIRNAME/obj/libogg" + cp -a -f "$DIRNAME/../lib/libogg/"* "$DIRNAME/obj/libogg" + + cd "$DIRNAME/obj/libogg" + ./configure --host=$HOST && + make $@ + check_error + touch "$DIRNAME/obj/libogg.stamp" +fi + +# Libvorbis +if [ ! -f "$DIRNAME/obj/libvorbis.stamp" ]; then + echo "Compiling libvorbis" + mkdir -p "$DIRNAME/obj/libvorbis" + cp -a -f "$DIRNAME/../lib/libvorbis/"* "$DIRNAME/obj/libvorbis" + + cd "$DIRNAME/obj/libvorbis" + CPPFLAGS="-I$DIRNAME/obj/libogg/include $CPPFLAGS" \ + LDFLAGS="-L$DIRNAME/obj/libogg/src/.libs $LDFLAGS" \ + ./configure --host=$HOST && + make $@ + check_error + touch "$DIRNAME/obj/libvorbis.stamp" +fi + +# STK +echo "Compiling STK" +cd "$DIRNAME" +${NDK_PATH}/ndk-build $@ \ + APP_BUILD_SCRIPT="$NDK_BUILD_SCRIPT" \ + APP_ABI="$NDK_ABI" \ + APP_PLATFORM="$NDK_PLATFORM" \ + APP_CPPFLAGS="$NDK_CPPFLAGS" \ + APP_STL=gnustl_static + +check_error + +# Build apk +echo "Building APK" +ant debug -Dsdk.dir="$SDK_PATH" -Dtarget=$NDK_PLATFORM +check_error diff --git a/android/res/drawable-hdpi/icon.png b/android/res/drawable-hdpi/icon.png new file mode 100644 index 000000000..33de3e803 Binary files /dev/null and b/android/res/drawable-hdpi/icon.png differ diff --git a/android/res/drawable-mdpi/icon.png b/android/res/drawable-mdpi/icon.png new file mode 100644 index 000000000..0a8f484e6 Binary files /dev/null and b/android/res/drawable-mdpi/icon.png differ diff --git a/android/res/drawable-xhdpi/icon.png b/android/res/drawable-xhdpi/icon.png new file mode 100644 index 000000000..6f4383226 Binary files /dev/null and b/android/res/drawable-xhdpi/icon.png differ diff --git a/android/res/drawable-xxhdpi/icon.png b/android/res/drawable-xxhdpi/icon.png new file mode 100644 index 000000000..7bbbc461b Binary files /dev/null and b/android/res/drawable-xxhdpi/icon.png differ diff --git a/android/res/drawable/icon.png b/android/res/drawable/icon.png new file mode 100644 index 000000000..a7b389fe7 Binary files /dev/null and b/android/res/drawable/icon.png differ diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml new file mode 100644 index 000000000..931a3c2c9 --- /dev/null +++ b/android/res/values/strings.xml @@ -0,0 +1,4 @@ + + + SuperTuxKart + diff --git a/cmake/Toolchain-android.cmake b/cmake/Toolchain-android.cmake new file mode 100644 index 000000000..0ab179eee --- /dev/null +++ b/cmake/Toolchain-android.cmake @@ -0,0 +1,32 @@ +# Cross-compiling requires CMake 2.6 or newer. Example: +# cmake .. -DCMAKE_TOOLCHAIN_FILE=../XCompile-Android.txt -DHOST=arm-linux-androideabi +# Where 'arm-linux-androideabi' is the host prefix for the cross-compiler. If +# you already have a toolchain file setup, you may use that instead of this +# file. Make sure to set CMAKE_FIND_ROOT_PATH to where the NDK toolchain was +# installed (e.g. "$ENV{HOME}/toolchains/arm-linux-androideabi-r10c-21"). + +# the name of the target operating system +SET(CMAKE_SYSTEM_NAME Linux) + +# which compilers to use for C and C++ +SET(CMAKE_C_COMPILER "${HOST}-gcc") +SET(CMAKE_CXX_COMPILER "${HOST}-g++") +SET(CMAKE_RC_COMPILER "${HOST}-windres") + +# here is the target environment located +SET(CMAKE_FIND_ROOT_PATH $ENV{NDK_TOOLCHAIN_PATH}) + +# here is where stuff gets installed to +SET(CMAKE_INSTALL_PREFIX "${CMAKE_FIND_ROOT_PATH}" CACHE STRING "Install path prefix, prepended onto install directories." FORCE) + +# adjust the default behaviour of the FIND_XXX() commands: +# search headers and libraries in the target environment, search +# programs in the host environment +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) + +# set env vars so that pkg-config will look in the appropriate directory for +# .pc files (as there seems to be no way to force using ${HOST}-pkg-config) +set(ENV{PKG_CONFIG_LIBDIR} "${CMAKE_INSTALL_PREFIX}/lib/pkgconfig") +set(ENV{PKG_CONFIG_PATH} "") diff --git a/lib/irrlicht/CMakeLists.txt b/lib/irrlicht/CMakeLists.txt index 03d2ec125..ac3e0d49c 100644 --- a/lib/irrlicht/CMakeLists.txt +++ b/lib/irrlicht/CMakeLists.txt @@ -112,6 +112,7 @@ source/Irrlicht/CImageLoaderPNG.cpp source/Irrlicht/CImageWriterBMP.cpp source/Irrlicht/CImageWriterJPG.cpp source/Irrlicht/CImageWriterPNG.cpp +source/Irrlicht/CIrrDeviceAndroid.cpp source/Irrlicht/CIrrDeviceConsole.cpp source/Irrlicht/CIrrDeviceFB.cpp source/Irrlicht/CIrrDeviceLinux.cpp @@ -243,6 +244,7 @@ source/Irrlicht/CImageLoaderPNG.h source/Irrlicht/CImageWriterBMP.h source/Irrlicht/CImageWriterJPG.h source/Irrlicht/CImageWriterPNG.h +source/Irrlicht/CIrrDeviceAndroid.h source/Irrlicht/CIrrDeviceConsole.h source/Irrlicht/CIrrDeviceFB.h source/Irrlicht/CIrrDeviceLinux.h diff --git a/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.cpp b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.cpp new file mode 100644 index 000000000..3e491ff4b --- /dev/null +++ b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.cpp @@ -0,0 +1,963 @@ +// Copyright (C) 2002-2007 Nikolaus Gebhardt +// Copyright (C) 2007-2011 Christian Stehno +// Copyright (C) 2016-2017 Dawid Gan +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#include "CIrrDeviceAndroid.h" + +#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_ + +#include +#include "os.h" +#include "CFileSystem.h" +#include "COGLES2Driver.h" + +namespace irr +{ + namespace video + { + IVideoDriver* createOGLES1Driver(const SIrrlichtCreationParameters& params, + video::SExposedVideoData& data, io::IFileSystem* io); + + IVideoDriver* createOGLES2Driver(const SIrrlichtCreationParameters& params, + video::SExposedVideoData& data, io::IFileSystem* io); + } +} + +namespace irr +{ + +// These variables must be global. Otherwise initialization will reach infinite +// loop after creating the device second time (i.e. the NULL driver and then +// GLES2 driver). We get initialization events from Android only once. +bool CIrrDeviceAndroid::IsPaused = true; +bool CIrrDeviceAndroid::IsFocused = false; +bool CIrrDeviceAndroid::IsStarted = false; +bool CIrrDeviceAndroid::IsClosing = false; + +//! constructor +CIrrDeviceAndroid::CIrrDeviceAndroid(const SIrrlichtCreationParameters& param) + : CIrrDeviceStub(param), + Accelerometer(0), + Gyroscope(0), + IsMousePressed(false) +{ + #ifdef _DEBUG + setDebugName("CIrrDeviceAndroid"); + #endif + + Android = (android_app *)(param.PrivateData); + + IsClosing = Android->destroyRequested; + + Android->userData = this; + Android->onAppCmd = handleAndroidCommand; + Android->onInputEvent = handleInput; + + SensorManager = ASensorManager_getInstance(); + SensorEventQueue = ASensorManager_createEventQueue(SensorManager, + Android->looper, LOOPER_ID_USER, NULL, NULL); + + ANativeActivity_setWindowFlags(Android->activity, + AWINDOW_FLAG_KEEP_SCREEN_ON | + AWINDOW_FLAG_FULLSCREEN, 0); + + createKeyMap(); + + // Create cursor control + CursorControl = new CCursorControl(); + + os::Printer::log("Waiting for Android activity window to be created.", ELL_DEBUG); + + while ((!IsStarted || !IsFocused || IsPaused) && !IsClosing) + { + s32 events = 0; + android_poll_source* source = 0; + + s32 id = ALooper_pollAll(-1, NULL, &events, (void**)&source); + + if (id >=0 && source != NULL) + { + source->process(Android, source); + } + } + + assert(Android->window); + os::Printer::log("Done", ELL_DEBUG); + + ExposedVideoData.OGLESAndroid.Window = Android->window; + + getVideoModeList(); + + createDriver(); + + if (VideoDriver) + createGUIAndScene(); +} + + +CIrrDeviceAndroid::~CIrrDeviceAndroid() +{ + Android->userData = NULL; +} + +video::IVideoModeList* CIrrDeviceAndroid::getVideoModeList() +{ + if (Android == NULL || Android->window == NULL) + return NULL; + + core::dimension2d size = core::dimension2d( + ANativeWindow_getWidth(Android->window), + ANativeWindow_getHeight(Android->window)); + + CreationParams.WindowSize.Width = size.Width; + CreationParams.WindowSize.Height = size.Height; + + if (!VideoModeList.getVideoModeCount()) + { + VideoModeList.addMode(size, 32); + VideoModeList.setDesktop(32, size); + } + + return &VideoModeList; +} + +void CIrrDeviceAndroid::createDriver() +{ + // Create the driver. + switch(CreationParams.DriverType) + { + case video::EDT_OGLES1: + #ifdef _IRR_COMPILE_WITH_OGLES1_ + VideoDriver = video::createOGLES1Driver(CreationParams, ExposedVideoData, FileSystem); + #else + os::Printer::log("No OpenGL ES 1.0 support compiled in.", ELL_ERROR); + #endif + break; + + case video::EDT_OGLES2: + #ifdef _IRR_COMPILE_WITH_OGLES2_ + VideoDriver = video::createOGLES2Driver(CreationParams, ExposedVideoData, FileSystem); + #else + os::Printer::log("No OpenGL ES 2.0 support compiled in.", ELL_ERROR); + #endif + break; + + case video::EDT_NULL: + VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize); + break; + + default: + os::Printer::log("Unable to create video driver of unknown type.", ELL_ERROR); + break; + } +} + +bool CIrrDeviceAndroid::run() +{ + os::Timer::tick(); + + while (!IsClosing) + { + s32 Events = 0; + android_poll_source* Source = 0; + bool should_run = (IsStarted && IsFocused && !IsPaused) || IsClosing; + s32 id = ALooper_pollAll(should_run ? 0 : -1, NULL, &Events, + (void**)&Source); + + if (id < 0) + break; + + if (Source) + { + Source->process(Android, Source); + } + + // if a sensor has data, we'll process it now. + if (id == LOOPER_ID_USER) + { + ASensorEvent event; + while (ASensorEventQueue_getEvents(SensorEventQueue, &event, 1) > 0) + { + switch (event.type) + { + case ASENSOR_TYPE_ACCELEROMETER: + SEvent accEvent; + accEvent.EventType = EET_ACCELEROMETER_EVENT; + accEvent.AccelerometerEvent.X = event.acceleration.x; + accEvent.AccelerometerEvent.Y = event.acceleration.y; + accEvent.AccelerometerEvent.Z = event.acceleration.z; + + postEventFromUser(accEvent); + break; + + case ASENSOR_TYPE_GYROSCOPE: + SEvent gyroEvent; + gyroEvent.EventType = EET_GYROSCOPE_EVENT; + gyroEvent.GyroscopeEvent.X = event.vector.x; + gyroEvent.GyroscopeEvent.Y = event.vector.y; + gyroEvent.GyroscopeEvent.Z = event.vector.z; + + postEventFromUser(gyroEvent); + break; + default: + break; + } + } + } + } + + return !IsClosing; +} + +void CIrrDeviceAndroid::yield() +{ + struct timespec ts = {0,1}; + nanosleep(&ts, NULL); +} + +void CIrrDeviceAndroid::sleep(u32 timeMs, bool pauseTimer) +{ + const bool wasStopped = Timer ? Timer->isStopped() : true; + + struct timespec ts; + ts.tv_sec = (time_t) (timeMs / 1000); + ts.tv_nsec = (long) (timeMs % 1000) * 1000000; + + if (pauseTimer && !wasStopped) + Timer->stop(); + + nanosleep(&ts, NULL); + + if (pauseTimer && !wasStopped) + Timer->start(); +} + +void CIrrDeviceAndroid::setWindowCaption(const wchar_t* text) +{ +} + +bool CIrrDeviceAndroid::present(video::IImage* surface, void* windowId, + core::rect* srcClip) +{ + return true; +} + +bool CIrrDeviceAndroid::isWindowActive() const +{ + return (IsFocused && !IsPaused); +} + +bool CIrrDeviceAndroid::isWindowFocused() const +{ + return IsFocused; +} + +bool CIrrDeviceAndroid::isWindowMinimized() const +{ + return IsPaused; +} + +void CIrrDeviceAndroid::closeDevice() +{ +} + +void CIrrDeviceAndroid::setResizable(bool resize) +{ +} + +void CIrrDeviceAndroid::minimizeWindow() +{ +} + +void CIrrDeviceAndroid::maximizeWindow() +{ +} + +void CIrrDeviceAndroid::restoreWindow() +{ +} + +E_DEVICE_TYPE CIrrDeviceAndroid::getType() const +{ + return EIDT_ANDROID; +} + +void CIrrDeviceAndroid::handleAndroidCommand(android_app* app, int32_t cmd) +{ + CIrrDeviceAndroid* device = (CIrrDeviceAndroid *)app->userData; + + switch (cmd) + { + case APP_CMD_SAVE_STATE: + os::Printer::log("Android command APP_CMD_SAVE_STATE", ELL_DEBUG); + break; + case APP_CMD_INIT_WINDOW: + os::Printer::log("Android command APP_CMD_INIT_WINDOW", ELL_DEBUG); + + if (device != NULL) + { + device->getExposedVideoData().OGLESAndroid.Window = app->window; + + // If the Android app is resumed, we need to re-create EGL surface + // to allow to draw something on it again. + if (device->VideoDriver != NULL && + device->CreationParams.DriverType == video::EDT_OGLES2) + { + video::COGLES2Driver* driver = (video::COGLES2Driver*)(device->VideoDriver); + driver->reloadEGLSurface(app->window); + } + } + + IsStarted = true; + break; + case APP_CMD_TERM_WINDOW: + os::Printer::log("Android command APP_CMD_TERM_WINDOW", ELL_DEBUG); + IsStarted = false; + break; + case APP_CMD_GAINED_FOCUS: + os::Printer::log("Android command APP_CMD_GAINED_FOCUS", ELL_DEBUG); + IsFocused = true; + break; + case APP_CMD_LOST_FOCUS: + os::Printer::log("Android command APP_CMD_LOST_FOCUS", ELL_DEBUG); + IsFocused = false; + break; + case APP_CMD_DESTROY: + os::Printer::log("Android command APP_CMD_DESTROY", ELL_DEBUG); + IsClosing = true; + // Make sure that state variables are set to the default state + // when the app is destroyed + IsPaused = true; + IsFocused = false; + IsStarted = false; + break; + case APP_CMD_PAUSE: + os::Printer::log("Android command APP_CMD_PAUSE", ELL_DEBUG); + IsPaused = true; + break; + case APP_CMD_RESUME: + os::Printer::log("Android command APP_CMD_RESUME", ELL_DEBUG); + IsPaused = false; + break; + case APP_CMD_START: + os::Printer::log("Android command APP_CMD_START", ELL_DEBUG); + break; + case APP_CMD_STOP: + os::Printer::log("Android command APP_CMD_STOP", ELL_DEBUG); + break; + case APP_CMD_WINDOW_RESIZED: + os::Printer::log("Android command APP_CMD_WINDOW_RESIZED", ELL_DEBUG); + break; + case APP_CMD_CONFIG_CHANGED: + os::Printer::log("Android command APP_CMD_CONFIG_CHANGED", ELL_DEBUG); + break; + case APP_CMD_LOW_MEMORY: + os::Printer::log("Android command APP_CMD_LOW_MEMORY", ELL_DEBUG); + break; + default: + break; + } + + if (device != NULL) + { + SEvent event; + event.EventType = EET_SYSTEM_EVENT; + event.SystemEvent.EventType = ESET_ANDROID_CMD; + event.SystemEvent.AndroidCmd.Cmd = cmd; + device->postEventFromUser(event); + } +} + +s32 CIrrDeviceAndroid::handleInput(android_app* app, AInputEvent* androidEvent) +{ + CIrrDeviceAndroid* device = (CIrrDeviceAndroid*)app->userData; + s32 status = 0; + + if (device == NULL) + return status; + + + switch (AInputEvent_getType(androidEvent)) + { + case AINPUT_EVENT_TYPE_MOTION: + { + SEvent event; + event.EventType = EET_TOUCH_INPUT_EVENT; + + s32 eventAction = AMotionEvent_getAction(androidEvent); + +#if 0 + // Useful for debugging. We might have to pass some of those infos on at some point. + // but preferably device independent (so iphone can use same irrlicht flags). + int32_t flags = AMotionEvent_getFlags(androidEvent); + os::Printer::log("flags: ", core::stringc(flags).c_str(), ELL_DEBUG); + int32_t metaState = AMotionEvent_getMetaState(androidEvent); + os::Printer::log("metaState: ", core::stringc(metaState).c_str(), ELL_DEBUG); + int32_t edgeFlags = AMotionEvent_getEdgeFlags(androidEvent); + os::Printer::log("edgeFlags: ", core::stringc(flags).c_str(), ELL_DEBUG); +#endif + + bool touchReceived = true; + bool simulate_mouse = false; + core::position2d mouse_pos = core::position2d(0, 0); + + switch (eventAction & AMOTION_EVENT_ACTION_MASK) + { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + event.TouchInput.Event = ETIE_PRESSED_DOWN; + break; + case AMOTION_EVENT_ACTION_MOVE: + event.TouchInput.Event = ETIE_MOVED; + break; + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + case AMOTION_EVENT_ACTION_CANCEL: + event.TouchInput.Event = ETIE_LEFT_UP; + break; + default: + touchReceived = false; + break; + } + + if (touchReceived) + { + s32 count = 1; + s32 idx = (eventAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> + AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + + // Process all touches for move action. + if (event.TouchInput.Event == ETIE_MOVED) + { + count = AMotionEvent_getPointerCount(androidEvent); + idx = 0; + } + + for (s32 i = 0; i < count; ++i) + { + event.TouchInput.ID = AMotionEvent_getPointerId(androidEvent, i + idx); + event.TouchInput.X = AMotionEvent_getX(androidEvent, i + idx); + event.TouchInput.Y = AMotionEvent_getY(androidEvent, i + idx); + + device->postEventFromUser(event); + + if (event.TouchInput.ID == 0) + { + simulate_mouse = true; + mouse_pos.X = event.TouchInput.X; + mouse_pos.Y = event.TouchInput.Y; + } + } + + status = 1; + } + + // Simulate mouse event for first finger on multitouch device. + // This allows to click on GUI elements. + if (simulate_mouse) + { + device->CursorControl->setPosition(mouse_pos); + + SEvent irrevent; + bool send_event = true; + + switch (event.TouchInput.Event) + { + case ETIE_PRESSED_DOWN: + irrevent.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN; + device->IsMousePressed = true; + break; + case ETIE_LEFT_UP: + irrevent.MouseInput.Event = EMIE_LMOUSE_LEFT_UP; + device->IsMousePressed = false; + break; + case ETIE_MOVED: + irrevent.MouseInput.Event = EMIE_MOUSE_MOVED; + break; + default: + send_event = false; + break; + } + + if (send_event) + { + irrevent.MouseInput.Control = false; + irrevent.MouseInput.Shift = false; + irrevent.MouseInput.ButtonStates = device->IsMousePressed ? + irr::EMBSM_LEFT : 0; + irrevent.EventType = EET_MOUSE_INPUT_EVENT; + irrevent.MouseInput.X = mouse_pos.X; + irrevent.MouseInput.Y = mouse_pos.Y; + + device->postEventFromUser(irrevent); + } + } + + break; + } + case AINPUT_EVENT_TYPE_KEY: + { + bool ignore_event = false; + + SEvent event; + event.EventType = EET_KEY_INPUT_EVENT; + event.KeyInput.Char = 0; + event.KeyInput.PressedDown = false; + event.KeyInput.Key = KEY_UNKNOWN; + + int32_t keyCode = AKeyEvent_getKeyCode(androidEvent); + int32_t keyAction = AKeyEvent_getAction(androidEvent); + int32_t keyMetaState = AKeyEvent_getMetaState(androidEvent); + int32_t keyRepeat = AKeyEvent_getRepeatCount(androidEvent); + + if (keyAction == AKEY_EVENT_ACTION_DOWN) + { + event.KeyInput.PressedDown = true; + } + else if (keyAction == AKEY_EVENT_ACTION_UP) + { + event.KeyInput.PressedDown = false; + } + else if (keyAction == AKEY_EVENT_ACTION_MULTIPLE) + { + // TODO: Multiple duplicate key events have occurred in a row, + // or a complex string is being delivered. The repeat_count + // property of the key event contains the number of times the + // given key code should be executed. + // I guess this might necessary for more complicated i18n key input, + // but don't see yet how to handle this correctly. + } + + event.KeyInput.Shift = (keyMetaState & AMETA_SHIFT_ON || + keyMetaState & AMETA_SHIFT_LEFT_ON || + keyMetaState & AMETA_SHIFT_RIGHT_ON); + + event.KeyInput.Control = (keyMetaState & AMETA_CTRL_ON || + keyMetaState & AMETA_CTRL_LEFT_ON || + keyMetaState & AMETA_CTRL_RIGHT_ON); + + event.KeyInput.SystemKeyCode = (u32)keyCode; + + if (keyCode >= 0 && (u32)keyCode < device->KeyMap.size()) + { + event.KeyInput.Key = device->KeyMap[keyCode]; + } + + if (event.KeyInput.Key > 0) + { + device->getKeyChar(event); + } + + // Handle an event when back button in pressed just like an escape key + // and also avoid repeating the event to avoid some strange behaviour + if (event.KeyInput.SystemKeyCode == AKEYCODE_BACK) + { + status = 1; + + if (event.KeyInput.PressedDown == false || keyRepeat > 0) + { + ignore_event = true; + } + } + + // Mark escape key event as handled by application to avoid receiving + // AKEYCODE_BACK key event + if (event.KeyInput.SystemKeyCode == AKEYCODE_ESCAPE) + { + status = 1; + } + + if (!ignore_event) + { + device->postEventFromUser(event); + } + + break; + } + default: + break; + } + + return status; +} + +video::SExposedVideoData& CIrrDeviceAndroid::getExposedVideoData() +{ + return ExposedVideoData; +} + +void CIrrDeviceAndroid::createKeyMap() +{ + KeyMap.set_used(223); + + KeyMap[0] = KEY_UNKNOWN; // AKEYCODE_UNKNOWN + KeyMap[1] = KEY_LBUTTON; // AKEYCODE_SOFT_LEFT + KeyMap[2] = KEY_RBUTTON; // AKEYCODE_SOFT_RIGHT + KeyMap[3] = KEY_HOME; // AKEYCODE_HOME + KeyMap[4] = KEY_ESCAPE; // AKEYCODE_BACK + KeyMap[5] = KEY_UNKNOWN; // AKEYCODE_CALL + KeyMap[6] = KEY_UNKNOWN; // AKEYCODE_ENDCALL + KeyMap[7] = KEY_KEY_0; // AKEYCODE_0 + KeyMap[8] = KEY_KEY_1; // AKEYCODE_1 + KeyMap[9] = KEY_KEY_2; // AKEYCODE_2 + KeyMap[10] = KEY_KEY_3; // AKEYCODE_3 + KeyMap[11] = KEY_KEY_4; // AKEYCODE_4 + KeyMap[12] = KEY_KEY_5; // AKEYCODE_5 + KeyMap[13] = KEY_KEY_6; // AKEYCODE_6 + KeyMap[14] = KEY_KEY_7; // AKEYCODE_7 + KeyMap[15] = KEY_KEY_8; // AKEYCODE_8 + KeyMap[16] = KEY_KEY_9; // AKEYCODE_9 + KeyMap[17] = KEY_UNKNOWN; // AKEYCODE_STAR + KeyMap[18] = KEY_UNKNOWN; // AKEYCODE_POUND + KeyMap[19] = KEY_UP; // AKEYCODE_DPAD_UP + KeyMap[20] = KEY_DOWN; // AKEYCODE_DPAD_DOWN + KeyMap[21] = KEY_LEFT; // AKEYCODE_DPAD_LEFT + KeyMap[22] = KEY_RIGHT; // AKEYCODE_DPAD_RIGHT + KeyMap[23] = KEY_SELECT; // AKEYCODE_DPAD_CENTER + KeyMap[24] = KEY_VOLUME_DOWN; // AKEYCODE_VOLUME_UP + KeyMap[25] = KEY_VOLUME_UP; // AKEYCODE_VOLUME_DOWN + KeyMap[26] = KEY_UNKNOWN; // AKEYCODE_POWER + KeyMap[27] = KEY_UNKNOWN; // AKEYCODE_CAMERA + KeyMap[28] = KEY_CLEAR; // AKEYCODE_CLEAR + KeyMap[29] = KEY_KEY_A; // AKEYCODE_A + KeyMap[30] = KEY_KEY_B; // AKEYCODE_B + KeyMap[31] = KEY_KEY_C; // AKEYCODE_C + KeyMap[32] = KEY_KEY_D; // AKEYCODE_D + KeyMap[33] = KEY_KEY_E; // AKEYCODE_E + KeyMap[34] = KEY_KEY_F; // AKEYCODE_F + KeyMap[35] = KEY_KEY_G; // AKEYCODE_G + KeyMap[36] = KEY_KEY_H; // AKEYCODE_H + KeyMap[37] = KEY_KEY_I; // AKEYCODE_I + KeyMap[38] = KEY_KEY_J; // AKEYCODE_J + KeyMap[39] = KEY_KEY_K; // AKEYCODE_K + KeyMap[40] = KEY_KEY_L; // AKEYCODE_L + KeyMap[41] = KEY_KEY_M; // AKEYCODE_M + KeyMap[42] = KEY_KEY_N; // AKEYCODE_N + KeyMap[43] = KEY_KEY_O; // AKEYCODE_O + KeyMap[44] = KEY_KEY_P; // AKEYCODE_P + KeyMap[45] = KEY_KEY_Q; // AKEYCODE_Q + KeyMap[46] = KEY_KEY_R; // AKEYCODE_R + KeyMap[47] = KEY_KEY_S; // AKEYCODE_S + KeyMap[48] = KEY_KEY_T; // AKEYCODE_T + KeyMap[49] = KEY_KEY_U; // AKEYCODE_U + KeyMap[50] = KEY_KEY_V; // AKEYCODE_V + KeyMap[51] = KEY_KEY_W; // AKEYCODE_W + KeyMap[52] = KEY_KEY_X; // AKEYCODE_X + KeyMap[53] = KEY_KEY_Y; // AKEYCODE_Y + KeyMap[54] = KEY_KEY_Z; // AKEYCODE_Z + KeyMap[55] = KEY_COMMA; // AKEYCODE_COMMA + KeyMap[56] = KEY_PERIOD; // AKEYCODE_PERIOD + KeyMap[57] = KEY_MENU; // AKEYCODE_ALT_LEFT + KeyMap[58] = KEY_MENU; // AKEYCODE_ALT_RIGHT + KeyMap[59] = KEY_LSHIFT; // AKEYCODE_SHIFT_LEFT + KeyMap[60] = KEY_RSHIFT; // AKEYCODE_SHIFT_RIGHT + KeyMap[61] = KEY_TAB; // AKEYCODE_TAB + KeyMap[62] = KEY_SPACE; // AKEYCODE_SPACE + KeyMap[63] = KEY_UNKNOWN; // AKEYCODE_SYM + KeyMap[64] = KEY_UNKNOWN; // AKEYCODE_EXPLORER + KeyMap[65] = KEY_UNKNOWN; // AKEYCODE_ENVELOPE + KeyMap[66] = KEY_RETURN; // AKEYCODE_ENTER + KeyMap[67] = KEY_BACK; // AKEYCODE_DEL + KeyMap[68] = KEY_OEM_3; // AKEYCODE_GRAVE + KeyMap[69] = KEY_MINUS; // AKEYCODE_MINUS + KeyMap[70] = KEY_UNKNOWN; // AKEYCODE_EQUALS + KeyMap[71] = KEY_UNKNOWN; // AKEYCODE_LEFT_BRACKET + KeyMap[72] = KEY_UNKNOWN; // AKEYCODE_RIGHT_BRACKET + KeyMap[73] = KEY_UNKNOWN; // AKEYCODE_BACKSLASH + KeyMap[74] = KEY_UNKNOWN; // AKEYCODE_SEMICOLON + KeyMap[75] = KEY_UNKNOWN; // AKEYCODE_APOSTROPHE + KeyMap[76] = KEY_UNKNOWN; // AKEYCODE_SLASH + KeyMap[77] = KEY_UNKNOWN; // AKEYCODE_AT + KeyMap[78] = KEY_UNKNOWN; // AKEYCODE_NUM + KeyMap[79] = KEY_UNKNOWN; // AKEYCODE_HEADSETHOOK + KeyMap[80] = KEY_UNKNOWN; // AKEYCODE_FOCUS (*Camera* focus) + KeyMap[81] = KEY_PLUS; // AKEYCODE_PLUS + KeyMap[82] = KEY_MENU; // AKEYCODE_MENU + KeyMap[83] = KEY_UNKNOWN; // AKEYCODE_NOTIFICATION + KeyMap[84] = KEY_UNKNOWN; // AKEYCODE_SEARCH + KeyMap[85] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PLAY_PAUSE + KeyMap[86] = KEY_MEDIA_STOP; // AKEYCODE_MEDIA_STOP + KeyMap[87] = KEY_MEDIA_NEXT_TRACK; // AKEYCODE_MEDIA_NEXT + KeyMap[88] = KEY_MEDIA_PREV_TRACK; // AKEYCODE_MEDIA_PREVIOUS + KeyMap[89] = KEY_UNKNOWN; // AKEYCODE_MEDIA_REWIND + KeyMap[90] = KEY_UNKNOWN; // AKEYCODE_MEDIA_FAST_FORWARD + KeyMap[91] = KEY_VOLUME_MUTE; // AKEYCODE_MUTE + KeyMap[92] = KEY_PRIOR; // AKEYCODE_PAGE_UP + KeyMap[93] = KEY_NEXT; // AKEYCODE_PAGE_DOWN + KeyMap[94] = KEY_UNKNOWN; // AKEYCODE_PICTSYMBOLS + KeyMap[95] = KEY_UNKNOWN; // AKEYCODE_SWITCH_CHARSET + + // following look like controller inputs + KeyMap[96] = KEY_UNKNOWN; // AKEYCODE_BUTTON_A + KeyMap[97] = KEY_UNKNOWN; // AKEYCODE_BUTTON_B + KeyMap[98] = KEY_UNKNOWN; // AKEYCODE_BUTTON_C + KeyMap[99] = KEY_UNKNOWN; // AKEYCODE_BUTTON_X + KeyMap[100] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Y + KeyMap[101] = KEY_UNKNOWN; // AKEYCODE_BUTTON_Z + KeyMap[102] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L1 + KeyMap[103] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R1 + KeyMap[104] = KEY_UNKNOWN; // AKEYCODE_BUTTON_L2 + KeyMap[105] = KEY_UNKNOWN; // AKEYCODE_BUTTON_R2 + KeyMap[106] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBL + KeyMap[107] = KEY_UNKNOWN; // AKEYCODE_BUTTON_THUMBR + KeyMap[108] = KEY_UNKNOWN; // AKEYCODE_BUTTON_START + KeyMap[109] = KEY_UNKNOWN; // AKEYCODE_BUTTON_SELECT + KeyMap[110] = KEY_UNKNOWN; // AKEYCODE_BUTTON_MODE + + KeyMap[111] = KEY_ESCAPE; // AKEYCODE_ESCAPE + KeyMap[112] = KEY_DELETE; // AKEYCODE_FORWARD_DEL + KeyMap[113] = KEY_CONTROL; // AKEYCODE_CTRL_LEFT + KeyMap[114] = KEY_CONTROL; // AKEYCODE_CTRL_RIGHT + KeyMap[115] = KEY_CAPITAL; // AKEYCODE_CAPS_LOCK + KeyMap[116] = KEY_SCROLL; // AKEYCODE_SCROLL_LOCK + KeyMap[117] = KEY_UNKNOWN; // AKEYCODE_META_LEFT + KeyMap[118] = KEY_UNKNOWN; // AKEYCODE_META_RIGHT + KeyMap[119] = KEY_UNKNOWN; // AKEYCODE_FUNCTION + KeyMap[120] = KEY_SNAPSHOT; // AKEYCODE_SYSRQ + KeyMap[121] = KEY_PAUSE; // AKEYCODE_BREAK + KeyMap[122] = KEY_HOME; // AKEYCODE_MOVE_HOME + KeyMap[123] = KEY_END; // AKEYCODE_MOVE_END + KeyMap[124] = KEY_INSERT; // AKEYCODE_INSERT + KeyMap[125] = KEY_UNKNOWN; // AKEYCODE_FORWARD + KeyMap[126] = KEY_PLAY; // AKEYCODE_MEDIA_PLAY + KeyMap[127] = KEY_MEDIA_PLAY_PAUSE; // AKEYCODE_MEDIA_PAUSE + KeyMap[128] = KEY_UNKNOWN; // AKEYCODE_MEDIA_CLOSE + KeyMap[129] = KEY_UNKNOWN; // AKEYCODE_MEDIA_EJECT + KeyMap[130] = KEY_UNKNOWN; // AKEYCODE_MEDIA_RECORD + KeyMap[131] = KEY_F1; // AKEYCODE_F1 + KeyMap[132] = KEY_F2; // AKEYCODE_F2 + KeyMap[133] = KEY_F3; // AKEYCODE_F3 + KeyMap[134] = KEY_F4; // AKEYCODE_F4 + KeyMap[135] = KEY_F5; // AKEYCODE_F5 + KeyMap[136] = KEY_F6; // AKEYCODE_F6 + KeyMap[137] = KEY_F7; // AKEYCODE_F7 + KeyMap[138] = KEY_F8; // AKEYCODE_F8 + KeyMap[139] = KEY_F9; // AKEYCODE_F9 + KeyMap[140] = KEY_F10; // AKEYCODE_F10 + KeyMap[141] = KEY_F11; // AKEYCODE_F11 + KeyMap[142] = KEY_F12; // AKEYCODE_F12 + KeyMap[143] = KEY_NUMLOCK; // AKEYCODE_NUM_LOCK + KeyMap[144] = KEY_NUMPAD0; // AKEYCODE_NUMPAD_0 + KeyMap[145] = KEY_NUMPAD1; // AKEYCODE_NUMPAD_1 + KeyMap[146] = KEY_NUMPAD2; // AKEYCODE_NUMPAD_2 + KeyMap[147] = KEY_NUMPAD3; // AKEYCODE_NUMPAD_3 + KeyMap[148] = KEY_NUMPAD4; // AKEYCODE_NUMPAD_4 + KeyMap[149] = KEY_NUMPAD5; // AKEYCODE_NUMPAD_5 + KeyMap[150] = KEY_NUMPAD6; // AKEYCODE_NUMPAD_6 + KeyMap[151] = KEY_NUMPAD7; // AKEYCODE_NUMPAD_7 + KeyMap[152] = KEY_NUMPAD8; // AKEYCODE_NUMPAD_8 + KeyMap[153] = KEY_NUMPAD9; // AKEYCODE_NUMPAD_9 + KeyMap[154] = KEY_DIVIDE; // AKEYCODE_NUMPAD_DIVIDE + KeyMap[155] = KEY_MULTIPLY; // AKEYCODE_NUMPAD_MULTIPLY + KeyMap[156] = KEY_SUBTRACT; // AKEYCODE_NUMPAD_SUBTRACT + KeyMap[157] = KEY_ADD; // AKEYCODE_NUMPAD_ADD + KeyMap[158] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_DOT + KeyMap[159] = KEY_COMMA; // AKEYCODE_NUMPAD_COMMA + KeyMap[160] = KEY_RETURN; // AKEYCODE_NUMPAD_ENTER + KeyMap[161] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_EQUALS + KeyMap[162] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_LEFT_PAREN + KeyMap[163] = KEY_UNKNOWN; // AKEYCODE_NUMPAD_RIGHT_PAREN + KeyMap[164] = KEY_VOLUME_MUTE; // AKEYCODE_VOLUME_MUTE + KeyMap[165] = KEY_UNKNOWN; // AKEYCODE_INFO + KeyMap[166] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_UP + KeyMap[167] = KEY_UNKNOWN; // AKEYCODE_CHANNEL_DOWN + KeyMap[168] = KEY_ZOOM; // AKEYCODE_ZOOM_IN + KeyMap[169] = KEY_UNKNOWN; // AKEYCODE_ZOOM_OUT + KeyMap[170] = KEY_UNKNOWN; // AKEYCODE_TV + KeyMap[171] = KEY_UNKNOWN; // AKEYCODE_WINDOW + KeyMap[172] = KEY_UNKNOWN; // AKEYCODE_GUIDE + KeyMap[173] = KEY_UNKNOWN; // AKEYCODE_DVR + KeyMap[174] = KEY_UNKNOWN; // AKEYCODE_BOOKMARK + KeyMap[175] = KEY_UNKNOWN; // AKEYCODE_CAPTIONS + KeyMap[176] = KEY_UNKNOWN; // AKEYCODE_SETTINGS + KeyMap[177] = KEY_UNKNOWN; // AKEYCODE_TV_POWER + KeyMap[178] = KEY_UNKNOWN; // AKEYCODE_TV_INPUT + KeyMap[179] = KEY_UNKNOWN; // AKEYCODE_STB_POWER + KeyMap[180] = KEY_UNKNOWN; // AKEYCODE_STB_INPUT + KeyMap[181] = KEY_UNKNOWN; // AKEYCODE_AVR_POWER + KeyMap[182] = KEY_UNKNOWN; // AKEYCODE_AVR_INPUT + KeyMap[183] = KEY_UNKNOWN; // AKEYCODE_PROG_RED + KeyMap[184] = KEY_UNKNOWN; // AKEYCODE_PROG_GREEN + KeyMap[185] = KEY_UNKNOWN; // AKEYCODE_PROG_YELLOW + KeyMap[186] = KEY_UNKNOWN; // AKEYCODE_PROG_BLUE + KeyMap[187] = KEY_UNKNOWN; // AKEYCODE_APP_SWITCH + KeyMap[188] = KEY_UNKNOWN; // AKEYCODE_BUTTON_1 + KeyMap[189] = KEY_UNKNOWN; // AKEYCODE_BUTTON_2 + KeyMap[190] = KEY_UNKNOWN; // AKEYCODE_BUTTON_3 + KeyMap[191] = KEY_UNKNOWN; // AKEYCODE_BUTTON_4 + KeyMap[192] = KEY_UNKNOWN; // AKEYCODE_BUTTON_5 + KeyMap[193] = KEY_UNKNOWN; // AKEYCODE_BUTTON_6 + KeyMap[194] = KEY_UNKNOWN; // AKEYCODE_BUTTON_7 + KeyMap[195] = KEY_UNKNOWN; // AKEYCODE_BUTTON_8 + KeyMap[196] = KEY_UNKNOWN; // AKEYCODE_BUTTON_9 + KeyMap[197] = KEY_UNKNOWN; // AKEYCODE_BUTTON_10 + KeyMap[198] = KEY_UNKNOWN; // AKEYCODE_BUTTON_11 + KeyMap[199] = KEY_UNKNOWN; // AKEYCODE_BUTTON_12 + KeyMap[200] = KEY_UNKNOWN; // AKEYCODE_BUTTON_13 + KeyMap[201] = KEY_UNKNOWN; // AKEYCODE_BUTTON_14 + KeyMap[202] = KEY_UNKNOWN; // AKEYCODE_BUTTON_15 + KeyMap[203] = KEY_UNKNOWN; // AKEYCODE_BUTTON_16 + KeyMap[204] = KEY_UNKNOWN; // AKEYCODE_LANGUAGE_SWITCH + KeyMap[205] = KEY_UNKNOWN; // AKEYCODE_MANNER_MODE + KeyMap[206] = KEY_UNKNOWN; // AKEYCODE_3D_MODE + KeyMap[207] = KEY_UNKNOWN; // AKEYCODE_CONTACTS + KeyMap[208] = KEY_UNKNOWN; // AKEYCODE_CALENDAR + KeyMap[209] = KEY_UNKNOWN; // AKEYCODE_MUSIC + KeyMap[210] = KEY_UNKNOWN; // AKEYCODE_CALCULATOR + KeyMap[211] = KEY_UNKNOWN; // AKEYCODE_ZENKAKU_HANKAKU + KeyMap[212] = KEY_UNKNOWN; // AKEYCODE_EISU + KeyMap[213] = KEY_UNKNOWN; // AKEYCODE_MUHENKAN + KeyMap[214] = KEY_UNKNOWN; // AKEYCODE_HENKAN + KeyMap[215] = KEY_UNKNOWN; // AKEYCODE_KATAKANA_HIRAGANA + KeyMap[216] = KEY_UNKNOWN; // AKEYCODE_YEN + KeyMap[217] = KEY_UNKNOWN; // AKEYCODE_RO + KeyMap[218] = KEY_UNKNOWN; // AKEYCODE_KANA + KeyMap[219] = KEY_UNKNOWN; // AKEYCODE_ASSIST + KeyMap[220] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_DOWN + KeyMap[221] = KEY_UNKNOWN; // AKEYCODE_BRIGHTNESS_UP , + KeyMap[222] = KEY_UNKNOWN; // AKEYCODE_MEDIA_AUDIO_TRACK +} + +void CIrrDeviceAndroid::getKeyChar(SEvent& event) +{ + // Handle ASCII chars + + event.KeyInput.Char = 0; + + // A-Z + if (event.KeyInput.SystemKeyCode > 28 && event.KeyInput.SystemKeyCode < 55) + { + if (event.KeyInput.Shift) + { + event.KeyInput.Char = event.KeyInput.SystemKeyCode + 36; + } + else + { + event.KeyInput.Char = event.KeyInput.SystemKeyCode + 68; + } + } + + // 0-9 + else if (event.KeyInput.SystemKeyCode > 6 && event.KeyInput.SystemKeyCode < 17) + { + event.KeyInput.Char = event.KeyInput.SystemKeyCode + 41; + } + + else if (event.KeyInput.SystemKeyCode == AKEYCODE_BACK) + { + event.KeyInput.Char = 8; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_DEL) + { + event.KeyInput.Char = 8; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_TAB) + { + event.KeyInput.Char = 9; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_ENTER) + { + event.KeyInput.Char = 13; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_SPACE) + { + event.KeyInput.Char = 32; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_COMMA) + { + event.KeyInput.Char = 44; + } + else if (event.KeyInput.SystemKeyCode == AKEYCODE_PERIOD) + { + event.KeyInput.Char = 46; + } +} + +bool CIrrDeviceAndroid::activateAccelerometer(float updateInterval) +{ + if (!isAccelerometerAvailable()) + return false; + + ASensorEventQueue_enableSensor(SensorEventQueue, Accelerometer); + ASensorEventQueue_setEventRate(SensorEventQueue, Accelerometer, + (int32_t)(updateInterval*1000.f*1000.f)); // in microseconds + + os::Printer::log("Activated accelerometer", ELL_DEBUG); + return true; +} + +bool CIrrDeviceAndroid::deactivateAccelerometer() +{ + if (!Accelerometer) + return false; + + ASensorEventQueue_disableSensor(SensorEventQueue, Accelerometer); + Accelerometer = 0; + os::Printer::log("Deactivated accelerometer", ELL_DEBUG); + return true; +} + +bool CIrrDeviceAndroid::isAccelerometerActive() +{ + return (Accelerometer != NULL); +} + +bool CIrrDeviceAndroid::isAccelerometerAvailable() +{ + if (!Accelerometer) + { + Accelerometer = ASensorManager_getDefaultSensor(SensorManager, + ASENSOR_TYPE_ACCELEROMETER); + } + + return (Accelerometer != NULL); +} + +bool CIrrDeviceAndroid::activateGyroscope(float updateInterval) +{ + if (!isGyroscopeAvailable()) + return false; + + ASensorEventQueue_enableSensor(SensorEventQueue, Gyroscope); + ASensorEventQueue_setEventRate(SensorEventQueue, Gyroscope, + (int32_t)(updateInterval*1000.f*1000.f)); // in microseconds + + os::Printer::log("Activated gyroscope", ELL_DEBUG); + return true; +} + +bool CIrrDeviceAndroid::deactivateGyroscope() +{ + if (!Gyroscope) + return false; + + ASensorEventQueue_disableSensor(SensorEventQueue, Gyroscope); + Gyroscope = 0; + os::Printer::log("Deactivated gyroscope", ELL_DEBUG); + return true; +} + +bool CIrrDeviceAndroid::isGyroscopeActive() +{ + return (Gyroscope != NULL); +} + +bool CIrrDeviceAndroid::isGyroscopeAvailable() +{ + if (!Gyroscope) + { + Gyroscope = ASensorManager_getDefaultSensor(SensorManager, + ASENSOR_TYPE_GYROSCOPE); + } + + return (Gyroscope != NULL); +} + + +} // end namespace irr + +#endif + + diff --git a/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h new file mode 100644 index 000000000..86d3ce1fe --- /dev/null +++ b/lib/irrlicht/source/Irrlicht/CIrrDeviceAndroid.h @@ -0,0 +1,126 @@ +// Copyright (C) 2002-2011 Nikolaus Gebhardt +// Copyright (C) 2016-2017 Dawid Gan +// This file is part of the "Irrlicht Engine". +// For conditions of distribution and use, see copyright notice in irrlicht.h + +#ifndef __C_IRR_DEVICE_ANDROID_H_INCLUDED__ +#define __C_IRR_DEVICE_ANDROID_H_INCLUDED__ + +#include "IrrCompileConfig.h" + +#ifdef _IRR_COMPILE_WITH_ANDROID_DEVICE_ + +#include +#include +#include +#include "CIrrDeviceStub.h" +#include "IrrlichtDevice.h" +#include "IImagePresenter.h" +#include "ICursorControl.h" + + +namespace irr +{ + + class CIrrDeviceAndroid : public CIrrDeviceStub, video::IImagePresenter + { + public: + //! constructor + CIrrDeviceAndroid(const SIrrlichtCreationParameters& param); + + //! destructor + virtual ~CIrrDeviceAndroid(); + + virtual bool run(); + virtual void yield(); + virtual void sleep(u32 timeMs, bool pauseTimer=false); + virtual void setWindowCaption(const wchar_t* text); + virtual bool present(video::IImage* surface, void* windowId, core::rect* srcClip); + virtual bool isWindowActive() const; + virtual bool isWindowFocused() const; + virtual bool isWindowMinimized() const; + virtual void closeDevice(); + virtual void setResizable( bool resize=false ); + virtual void minimizeWindow(); + virtual void maximizeWindow(); + virtual void restoreWindow(); + virtual E_DEVICE_TYPE getType() const; + virtual bool activateAccelerometer(float updateInterval); + virtual bool deactivateAccelerometer(); + virtual bool isAccelerometerActive(); + virtual bool isAccelerometerAvailable(); + virtual bool activateGyroscope(float updateInterval); + virtual bool deactivateGyroscope(); + virtual bool isGyroscopeActive(); + virtual bool isGyroscopeAvailable(); + video::IVideoModeList* getVideoModeList(); + + class CCursorControl : public gui::ICursorControl + { + public: + + CCursorControl() : CursorPos(core::position2d(0, 0)) {} + virtual void setVisible(bool visible) {} + virtual bool isVisible() const {return false;} + virtual void setPosition(const core::position2d &pos) + { + setPosition(pos.X, pos.Y); + } + virtual void setPosition(f32 x, f32 y) + { + CursorPos.X = x; + CursorPos.Y = y; + } + virtual void setPosition(const core::position2d &pos) + { + setPosition(pos.X, pos.Y); + } + virtual void setPosition(s32 x, s32 y) + { + CursorPos.X = x; + CursorPos.Y = y; + } + virtual const core::position2d& getPosition() + { + return CursorPos; + } + virtual core::position2d getRelativePosition() + { + return core::position2d(0, 0); + } + virtual void setReferenceRect(core::rect* rect=0) {} + private: + core::position2d CursorPos; + }; + + private: + android_app* Android; + ASensorManager* SensorManager; + ASensorEventQueue* SensorEventQueue; + const ASensor* Accelerometer; + const ASensor* Gyroscope; + + static bool IsPaused; + static bool IsFocused; + static bool IsStarted; + static bool IsClosing; + + bool IsMousePressed; + + video::SExposedVideoData ExposedVideoData; + + core::array KeyMap; + + void createDriver(); + void createKeyMap(); + void getKeyChar(SEvent& event); + video::SExposedVideoData& getExposedVideoData(); + + static void handleAndroidCommand(android_app* app, int32_t cmd); + static s32 handleInput(android_app* app, AInputEvent* event); + }; + +} // end namespace irr + +#endif // _IRR_COMPILE_WITH_ANDROID_DEVICE_ +#endif // __C_IRR_DEVICE_ANDROID_H_INCLUDED__ diff --git a/lib/libpng/CMakeLists.txt b/lib/libpng/CMakeLists.txt index b8d476b12..68aa98cac 100644 --- a/lib/libpng/CMakeLists.txt +++ b/lib/libpng/CMakeLists.txt @@ -60,6 +60,7 @@ if(NOT WIN32) if(NOT M_LIBRARY) message(STATUS "math library 'libm' not found - floating point support disabled") + set(M_LIBRARY "") endif() else() # not needed on windows diff --git a/sources.cmake b/sources.cmake index d4f28ae4d..ddc029d4f 100644 --- a/sources.cmake +++ b/sources.cmake @@ -1,4 +1,4 @@ -# Modify this file to change the last-modified date when you add/remove a file. +# Modify this file to change the last-modified date when you add/remove a file. # This will then trigger a new cmake run automatically. file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp") file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp") diff --git a/src/io/assets_android.cpp b/src/io/assets_android.cpp new file mode 100644 index 000000000..11f6f70fd --- /dev/null +++ b/src/io/assets_android.cpp @@ -0,0 +1,433 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2014-2015 SuperTuxKart-Team +// +// This program 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 3 +// 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. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "graphics/irr_driver.hpp" +#include "io/assets_android.hpp" +#include "io/file_manager.hpp" +#include "utils/log.hpp" +#include "utils/progress_bar_android.hpp" + +#include +#include +#include + +#ifdef ANDROID +#include +#endif + +//----------------------------------------------------------------------------- +/** Assets Android constructor + */ +AssetsAndroid::AssetsAndroid(FileManager* file_manager) +{ + m_file_manager = file_manager; +} + +//----------------------------------------------------------------------------- +/** A function that detects a path where data directory is placed and that + * sets some environment variables that are used for finding data and + * home directory in a common code. + */ +void AssetsAndroid::init() +{ +#ifdef ANDROID + if (m_file_manager == NULL) + return; + + bool needs_extract_data = false; + const std::string version = std::string("supertuxkart.") + STK_VERSION; + + // Add some paths to check + std::vector paths; + + if (getenv("SUPERTUXKART_DATADIR")) + paths.push_back(getenv("SUPERTUXKART_DATADIR")); + + if (getenv("EXTERNAL_STORAGE")) + paths.push_back(getenv("EXTERNAL_STORAGE")); + + if (getenv("SECONDARY_STORAGE")) + paths.push_back(getenv("SECONDARY_STORAGE")); + + if (global_android_app->activity->externalDataPath) + paths.push_back(global_android_app->activity->externalDataPath); + + if (global_android_app->activity->internalDataPath) + paths.push_back(global_android_app->activity->internalDataPath); + + paths.push_back("/sdcard/"); + paths.push_back("/storage/sdcard0/"); + paths.push_back("/storage/sdcard1/"); + paths.push_back("/data/data/org.supertuxkart.stk/files/"); + + // Check if STK data is available somewhere + for (std::string path : paths) + { + Log::info("AssetsAndroid", "Check data files in: %s", path.c_str()); + + if (m_file_manager->fileExists(path + "/stk/data/" + version)) + { + Log::info("AssetsAndroid", "Data files found in: %s", path.c_str()); + m_stk_dir = path + "/stk"; + break; + } + + if (m_file_manager->fileExists(path + "/supertuxkart/data/" + version)) + { + Log::info("AssetsAndroid", "Data files found in: %s", path.c_str()); + m_stk_dir = path + "/supertuxkart"; + break; + } + } + + // Create data dir if it's not available anywhere + if (m_stk_dir.size() == 0) + { + for (std::string path : paths) + { + if (m_file_manager->checkAndCreateDirectoryP(path + "/stk/data")) + { + Log::info("AssetsAndroid", "Data directory created in: %s", + path.c_str()); + m_stk_dir = path + "/stk"; + needs_extract_data = true; + break; + } + } + } + + // We can't continue if STK dir has not been found + if (m_stk_dir.size() == 0) + { + Log::fatal("AssetsAndroid", "Fatal error: Couldn't find Supertuxkart " + "data directory"); + } + + // Check if assets were extracted properly + if (!m_file_manager->fileExists(m_stk_dir + "/.extracted") && + !needs_extract_data) + { + needs_extract_data = true; + Log::warn("AssetsAndroid", "Assets seem to be not extracted properly, " + "because the .extracted file doesn't exist. Force " + "extracting assets..."); + } + + if (!m_file_manager->checkAndCreateDirectoryP(m_stk_dir + "/home")) + { + Log::warn("AssetsAndroid", "Couldn't create home directory"); + } + + // Set some useful variables + setenv("SUPERTUXKART_DATADIR", m_stk_dir.c_str(), 1); + setenv("HOME", (m_stk_dir + "/home").c_str(), 1); + setenv("XDG_CONFIG_HOME", (m_stk_dir + "/home").c_str(), 1); + + // Extract data directory from apk if it's needed + if (needs_extract_data) + { + removeData(); + extractData(); + + if (!m_file_manager->fileExists(m_stk_dir + "/.extracted")) + { + Log::fatal("AssetsAndroid", "Fatal error: Assets were not " + "extracted properly"); + } + } + +#endif +} + +//----------------------------------------------------------------------------- +/** A function that extracts whole data directory from apk file to a real + * path in the filesystem + */ +void AssetsAndroid::extractData() +{ +#ifdef ANDROID + const std::string dirs_list = "directories.txt"; + + bool success = true; + + // Create .nomedia file + touchFile(m_stk_dir + "/.nomedia"); + + // Extract base directory first, so that we will be able to open the file + // with dir names + success = extractDir(""); + + if (!success) + { + Log::error("AssetsAndroid", "Error: Couldn't extract main directory."); + return; + } + + std::fstream file(m_stk_dir + "/" + dirs_list, std::ios::in); + + if (file.good()) + { + unsigned int lines_count = 0; + + while (!file.eof()) + { + std::string dir_name; + getline(file, dir_name); + + if (dir_name.length() == 0 || dir_name.at(0) == '#') + continue; + + lines_count++; + } + + if (lines_count > 0) + { + file.clear(); + file.seekg(0, std::ios::beg); + + ProgressBarAndroid* progress_bar = new ProgressBarAndroid(); + progress_bar->draw(0.0f); + unsigned int current_line = 1; + + while (!file.eof()) + { + std::string dir_name; + getline(file, dir_name); + + if (dir_name.length() == 0 || dir_name.at(0) == '#') + continue; + + success = extractDir(dir_name); + + assert(lines_count > 0); + progress_bar->draw((float)(current_line) / lines_count); + current_line++; + + if (progress_bar->closeEventReceived()) + { + success = false; + } + + if (!success) + break; + } + + delete progress_bar; + } + } + else + { + Log::warn("AssetsAndroid", "Warning: Cannot open %s file. Ignoring " + "extraction of other directories.", dirs_list.c_str()); + } + + file.close(); + + // Mark the extraction as successful if everything is ok + if (success) + { + touchFile(m_stk_dir + "/.extracted"); + } +#endif +} + +//----------------------------------------------------------------------------- +/** A function that extracts selected directory from apk file + * \param dir_name Directory to extract from assets + * \return True if successfully extracted + */ +bool AssetsAndroid::extractDir(std::string dir_name) +{ +#ifdef ANDROID + AAssetManager* amgr = global_android_app->activity->assetManager; + + Log::info("AssetsAndroid", "Extracting %s directory", + dir_name.length() > 0 ? dir_name.c_str() : "main"); + + std::string output_dir = dir_name; + + if (m_stk_dir.length() > 0) + { + output_dir = m_stk_dir + "/" + dir_name; + } + + AAssetDir* asset_dir = AAssetManager_openDir(amgr, dir_name.c_str()); + + if (asset_dir == NULL) + { + Log::warn("AssetsAndroid", "Couldn't get asset dir: %s", + dir_name.c_str()); + return true; + } + + if (!m_file_manager->checkAndCreateDirectoryP(output_dir)) + { + Log::warn("AssetsAndroid", "Couldn't create %s directory", + output_dir.c_str()); + return false; + } + + const int buf_size = 65536; + char* buf = new char[buf_size](); + bool extraction_failed = false; + + while (!extraction_failed) + { + const char* filename = AAssetDir_getNextFileName(asset_dir); + + // Check if finished + if (filename == NULL) + break; + + if (strlen(filename) == 0) + continue; + + std::string file_path = std::string(filename); + + if (dir_name.length() > 0) + { + file_path = dir_name + "/" + std::string(filename); + } + + AAsset* asset = AAssetManager_open(amgr, file_path.c_str(), + AASSET_MODE_STREAMING); + + if (asset == NULL) + { + Log::warn("AssetsAndroid", "Asset is null: %s", filename); + continue; + } + + std::string output_path = output_dir + "/" + std::string(filename); + std::fstream out_file(output_path, std::ios::out | std::ios::binary); + + if (!out_file.good()) + { + extraction_failed = true; + Log::error("AssetsAndroid", "Couldn't create a file: %s", filename); + AAsset_close(asset); + break; + } + + int nb_read = 0; + while ((nb_read = AAsset_read(asset, buf, buf_size)) > 0) + { + out_file.write(buf, nb_read); + + if (out_file.fail()) + { + extraction_failed = true; + break; + } + } + + out_file.close(); + + if (out_file.fail() || extraction_failed) + { + extraction_failed = true; + Log::error("AssetsAndroid", "Extraction failed for file: %s", + filename); + } + + AAsset_close(asset); + } + + delete[] buf; + + AAssetDir_close(asset_dir); + + return !extraction_failed; +#endif + + return false; +} + +//----------------------------------------------------------------------------- +/** A function that removes whole STK data directory + */ +void AssetsAndroid::removeData() +{ +#ifdef ANDROID + if (m_stk_dir.length() == 0) + return; + + // Make sure that we are not accidentally removing wrong directory + if (m_stk_dir.find("/stk") == std::string::npos && + m_stk_dir.find("/supertuxkart") == std::string::npos) + { + Log::error("AssetsAndroid", "Invalid data directory: %s", + m_stk_dir.c_str()); + assert(false); + return; + } + + std::set files; + m_file_manager->listFiles(files, m_stk_dir, true); + + for (std::string file : files) + { + if (file == m_stk_dir + "/." || file == m_stk_dir + "/..") + continue; + + // Don't delete home directory that contains configuration files + // and add-ons + if (file == m_stk_dir + "/home") + continue; + + // Don't delete .nomedia file. It has a sense to keep it for home + // directory, i.e. for textures of add-on karts etc. + if (file == m_stk_dir + "/.nomedia") + continue; + + Log::info("AssetsAndroid", "Deleting file: %s\n", file.c_str()); + + if (m_file_manager->isDirectory(file)) + { + m_file_manager->removeDirectory(file); + } + else + { + m_file_manager->removeFile(file); + } + } +#endif +} + +//----------------------------------------------------------------------------- +/** A function that creates empty file + * \param path A path to the file that should be created + */ +void AssetsAndroid::touchFile(std::string path) +{ +#ifdef ANDROID + if (m_file_manager->fileExists(path)) + return; + + std::fstream file(path, std::ios::out | std::ios::binary); + + if (!file.good()) + { + Log::warn("AssetsAndroid", "Error: Cannot create %s file.", + path.c_str()); + } + + file.close(); +#endif +} + +//----------------------------------------------------------------------------- diff --git a/src/io/assets_android.hpp b/src/io/assets_android.hpp new file mode 100644 index 000000000..0cccf566a --- /dev/null +++ b/src/io/assets_android.hpp @@ -0,0 +1,44 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2014-2015 SuperTuxKart-Team +// +// This program 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 3 +// 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. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef HEADER_ASSETS_ANDROID_HPP +#define HEADER_ASSETS_ANDROID_HPP + +#include + +class FileManager; + +class AssetsAndroid +{ +private: + FileManager* m_file_manager; + std::string m_stk_dir; + + void extractData(); + bool extractDir(std::string dir_name); + void removeData(); + void touchFile(std::string path); + +public: + AssetsAndroid(FileManager* file_manager); + ~AssetsAndroid() {}; + + void init(); +}; + + +#endif diff --git a/src/main_android.cpp b/src/main_android.cpp new file mode 100644 index 000000000..ec3505584 --- /dev/null +++ b/src/main_android.cpp @@ -0,0 +1,62 @@ +#ifdef ANDROID + +#include "config/user_config.hpp" +#include "graphics/irr_driver.hpp" +#include "utils/log.hpp" + + +extern int main(int argc, char *argv[]); + +void override_default_params() +{ + // It has an effect only on the first run, when config file is created. + // So that we can still modify these params in STK options and user's + // choice will be then remembered. + + // Set smaller texture size to avoid high RAM usage + UserConfigParams::m_max_texture_size = 256; + UserConfigParams::m_high_definition_textures = false; + + // Disable advanced lighting by default to make the game playable + UserConfigParams::m_dynamic_lights = false; + + // Enable touch steering and screen keyboard + UserConfigParams::m_multitouch_enabled = true; + UserConfigParams::m_screen_keyboard = true; + + // It shouldn't matter, but STK is always run in fullscreen on android + UserConfigParams::m_fullscreen = true; + + // Make sure that user can play every track even if there are installed + // only few tracks and it's impossible to finish overworld challenges + UserConfigParams::m_everything_unlocked = true; + + // Create default user istead of showing login screen to make life easier + UserConfigParams::m_enforce_current_player = true; + + // Just for debugging + UserConfigParams::m_log_errors_to_console = true; +} + +void android_main(struct android_app* app) +{ + Log::info("AndroidMain", "Loading application..."); + + app_dummy(); + + override_default_params(); + + global_android_app = app; + main(0, {}); + + Log::info("AndroidMain", "Closing STK..."); + + // TODO: Irrlicht device is properly waiting for destroy event, but + // some global variables are not initialized/cleared in functions and thus + // its state is remembered when the window is restored. We will use exit + // call to make sure that all variables are cleared until a proper fix will + // be done. + exit(0); +} + +#endif diff --git a/src/utils/progress_bar_android.cpp b/src/utils/progress_bar_android.cpp new file mode 100644 index 000000000..1636c6e37 --- /dev/null +++ b/src/utils/progress_bar_android.cpp @@ -0,0 +1,205 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2014-2015 SuperTuxKart-Team +// +// This program 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 3 +// 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. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#include "config/user_config.hpp" +#include "graphics/irr_driver.hpp" +#include "utils/log.hpp" +#include "utils/progress_bar_android.hpp" + +#ifdef ANDROID + +ProgressBarAndroid::ProgressBarAndroid() +{ + m_program = 0; + m_vertex_shader = 0; + m_fragment_shader = 0; + m_position = 0; + m_progress = 0; + m_vbo = 0; + m_device = NULL; + m_initialized = false; + m_close_event_received = false; + + init(); +} + +ProgressBarAndroid::~ProgressBarAndroid() +{ + close(); +} + +bool ProgressBarAndroid::compileShaders() +{ + const GLchar* vsh = + "precision mediump float;" + "attribute vec2 position;" + "uniform float progress;" + "void main(void)" + "{" + " float pos_x = (position.x + 1.0) * progress - 1.0;" + " float pos_y = position.y * 0.1 - 0.6;" + " gl_Position = vec4(pos_x, pos_y, 0.0, 1.0);" + "}"; + + const GLchar* fsh = + "precision mediump float;" + "void main(void)" + "{" + " gl_FragColor = vec4(0.5, 0.5, 0.5, 1.0);" + "}"; + + m_vertex_shader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(m_vertex_shader, 1, &vsh, NULL); + glCompileShader(m_vertex_shader); + + GLint success; + glGetShaderiv(m_vertex_shader, GL_COMPILE_STATUS, &success); + if (!success) + { + Log::error("ProgressBarAndroid", "Failed to compile vertex shader."); + return false; + } + + m_fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(m_fragment_shader, 1, &fsh, NULL); + glCompileShader(m_fragment_shader); + + glGetShaderiv(m_fragment_shader, GL_COMPILE_STATUS, &success); + if (!success) + { + Log::error("ProgressBarAndroid", "Failed to compile fragment shader."); + return false; + } + + m_program = glCreateProgram(); + + glAttachShader(m_program, m_vertex_shader); + glAttachShader(m_program, m_fragment_shader); + glLinkProgram(m_program); + + glGetProgramiv(m_program, GL_LINK_STATUS, &success); + if (!success) + { + Log::error("ProgressBarAndroid", "Failed to link program."); + return false; + } + + m_position = glGetAttribLocation(m_program, "position"); + if (m_position == -1) + { + Log::error("ProgressBarAndroid", "Failed to get attrib location."); + return false; + } + + m_progress = glGetUniformLocation(m_program, "progress"); + if (m_progress == -1) + { + Log::error("ProgressBarAndroid", "Failed to get uniform location."); + return false; + } + + return true; +} + +void ProgressBarAndroid::deleteShaders() +{ + glDeleteShader(m_vertex_shader); + glDeleteShader(m_fragment_shader); + glDeleteProgram(m_program); +} + +void ProgressBarAndroid::init() +{ + SIrrlichtCreationParameters params; + params.DriverType = video::EDT_OGLES2; + params.Bits = 32; + params.Fullscreen = UserConfigParams::m_fullscreen; + params.WindowSize = core::dimension2du(640, 480); +#if defined(ANDROID) + params.PrivateData = (void*)global_android_app; +#endif + + m_device = createDeviceEx(params); + + if (!m_device) + return; + + bool success = compileShaders(); + + if (!success) + return; + + const GLfloat vertices[] = + { + -1, 1, 1, -1, -1, -1, + 1, -1, -1, 1, 1, 1 + }; + + glGenBuffers(1, &m_vbo); + glBindBuffer(GL_ARRAY_BUFFER, m_vbo); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glUseProgram(m_program); + glEnableVertexAttribArray(m_position); + glVertexAttribPointer(m_position, 2, GL_FLOAT, GL_FALSE, 0, 0); + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + m_initialized = true; +} + +void ProgressBarAndroid::close() +{ + glDisableVertexAttribArray(m_position); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glUseProgram(0); + + deleteShaders(); + + if (m_device != NULL) + { + m_device->closeDevice(); + m_device->clearSystemMessages(); + m_device->run(); + m_device->drop(); + m_device = NULL; + } + + m_initialized = false; +} + +void ProgressBarAndroid::draw(float value) +{ + if (!m_initialized || m_close_event_received) + return; + + value = value > 1.0f ? 1.0f : value; + + m_close_event_received = !m_device->run(); + + m_device->getVideoDriver()->beginScene(true, true); + + glClear(GL_COLOR_BUFFER_BIT); + glUniform1f(m_progress, value); + glDrawArrays(GL_TRIANGLES, 0, 6); + + m_device->getVideoDriver()->endScene(); +} + +#endif diff --git a/src/utils/progress_bar_android.hpp b/src/utils/progress_bar_android.hpp new file mode 100644 index 000000000..b712457c4 --- /dev/null +++ b/src/utils/progress_bar_android.hpp @@ -0,0 +1,55 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2014-2015 SuperTuxKart-Team +// +// This program 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 3 +// 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. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef HEADER_PROGRESS_BAR_ANDROID_HPP +#define HEADER_PROGRESS_BAR_ANDROID_HPP + +#ifdef ANDROID + +#include "IrrlichtDevice.h" +#include "graphics/gl_headers.hpp" + +class ProgressBarAndroid +{ +private: + GLuint m_program; + GLuint m_vertex_shader; + GLuint m_fragment_shader; + GLint m_position; + GLint m_progress; + GLuint m_vbo; + + irr::IrrlichtDevice* m_device; + bool m_initialized; + bool m_close_event_received; + + bool compileShaders(); + void deleteShaders(); + void init(); + void close(); + +public: + ProgressBarAndroid(); + ~ProgressBarAndroid(); + + void draw(float value); + bool closeEventReceived() {return m_close_event_received;} +}; + +#endif + +#endif