From 139989107c5201fc57e7154b9a8d73d3371303c2 Mon Sep 17 00:00:00 2001 From: XProger Date: Tue, 7 Mar 2017 01:41:11 +0300 Subject: [PATCH] #15 android project --- src/core.h | 8 +- src/game.h | 13 +- src/lara.h | 2 +- src/level.h | 47 +-- src/platform/android/.classpath | 9 + src/platform/android/.cproject | 48 +++ src/platform/android/.project | 49 +++ .../org.eclipse.cdt.codan.core.prefs | 72 +++++ .../.settings/org.eclipse.jdt.core.prefs | 4 + src/platform/android/AndroidManifest.xml | 29 ++ src/platform/android/jni/Android.mk | 17 + src/platform/android/jni/Application.mk | 8 + src/platform/android/jni/main.cpp | 145 +++++++++ src/platform/android/lint.xml | 4 + src/platform/android/proguard-project.txt | 144 +++++++++ src/platform/android/project.properties | 14 + .../android/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 9397 bytes .../android/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 5237 bytes .../res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 14383 bytes src/platform/android/res/values/strings.xml | 5 + .../org/xproger/openlara/MainActivity.java | 306 ++++++++++++++++++ src/platform/web/build.bat | 2 +- src/shader.glsl | 29 +- src/texture.h | 9 +- src/utils.h | 2 +- src/water.glsl | 29 +- 26 files changed, 929 insertions(+), 66 deletions(-) create mode 100644 src/platform/android/.classpath create mode 100644 src/platform/android/.cproject create mode 100644 src/platform/android/.project create mode 100644 src/platform/android/.settings/org.eclipse.cdt.codan.core.prefs create mode 100644 src/platform/android/.settings/org.eclipse.jdt.core.prefs create mode 100644 src/platform/android/AndroidManifest.xml create mode 100644 src/platform/android/jni/Android.mk create mode 100644 src/platform/android/jni/Application.mk create mode 100644 src/platform/android/jni/main.cpp create mode 100644 src/platform/android/lint.xml create mode 100644 src/platform/android/proguard-project.txt create mode 100644 src/platform/android/project.properties create mode 100644 src/platform/android/res/drawable-hdpi/ic_launcher.png create mode 100644 src/platform/android/res/drawable-mdpi/ic_launcher.png create mode 100644 src/platform/android/res/drawable-xhdpi/ic_launcher.png create mode 100644 src/platform/android/res/values/strings.xml create mode 100644 src/platform/android/src/org/xproger/openlara/MainActivity.java diff --git a/src/core.h b/src/core.h index 5b10df2..5df244b 100644 --- a/src/core.h +++ b/src/core.h @@ -212,6 +212,7 @@ namespace Core { bool depthTexture; bool shadowSampler; bool discardFrame; + bool texNPOT; bool texFloat, texFloatLinear; bool texHalf, texHalfLinear; } support; @@ -296,11 +297,12 @@ namespace Core { char *ext = (char*)glGetString(GL_EXTENSIONS); - + LOG("%s\n", ext); support.VAO = extSupport(ext, "_vertex_array_object"); support.depthTexture = extSupport(ext, "_depth_texture"); - support.shadowSampler = false;//extSupport(ext, "_shadow_samplers") || extSupport(ext, "GL_ARB_shadow"); + support.shadowSampler = extSupport(ext, "_shadow_samplers") || extSupport(ext, "GL_ARB_shadow"); support.discardFrame = extSupport(ext, "_discard_framebuffer"); + support.texNPOT = extSupport(ext, "_texture_npot") || extSupport(ext, "_texture_non_power_of_two"); support.texFloatLinear = extSupport(ext, "GL_ARB_texture_float") || extSupport(ext, "_texture_float_linear"); support.texFloat = support.texFloatLinear || extSupport(ext, "_texture_float"); support.texHalfLinear = extSupport(ext, "GL_ARB_texture_float") || extSupport(ext, "_texture_half_float_linear"); @@ -316,6 +318,7 @@ namespace Core { LOG(" depth texture : %s\n", support.depthTexture ? "true" : "false"); LOG(" shadow sampler : %s\n", support.shadowSampler ? "true" : "false"); LOG(" discard frame : %s\n", support.discardFrame ? "true" : "false"); + LOG(" NPOT textures : %s\n", support.texNPOT ? "true" : "false"); LOG(" float textures : float = %s, half = %s\n", support.texFloat ? (support.texFloatLinear ? "linear" : "nearest") : "false", support.texHalf ? (support.texHalfLinear ? "linear" : "nearest") : "false"); @@ -401,7 +404,6 @@ namespace Core { } void setBlending(BlendMode mode) { - mode = bmNone; switch (mode) { case bmNone : glDisable(GL_BLEND); diff --git a/src/game.h b/src/game.h index 8123e79..49db3db 100644 --- a/src/game.h +++ b/src/game.h @@ -13,10 +13,9 @@ namespace Game { level = new Level(*lvl, demo, home); delete lvl; - #ifndef __EMSCRIPTEN__ - //Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), 1, 1, 0); - Sound::play(snd, vec3(0.0f), 1, 1, Sound::Flags::LOOP); - #endif + + //Sound::play(Sound::openWAD("05_Lara's_Themes.wav"), 1, 1, 0); + Sound::play(snd, vec3(0.0f), 1, 1, Sound::Flags::LOOP); } void init(Stream *lvl, Stream *snd) { @@ -27,8 +26,10 @@ namespace Game { void init(char *lvlName = NULL, char *sndName = NULL) { if (!lvlName) lvlName = (char*)"LEVEL2.PSX"; - if (!sndName) sndName = (char*)"05.ogg"; - init(new Stream(lvlName), new Stream(sndName)); + #ifndef __EMSCRIPTEN__ + if (!sndName) sndName = (char*)"05.ogg"; + #endif + init(new Stream(lvlName), sndName ? new Stream(sndName) : NULL); } void free() { diff --git a/src/lara.h b/src/lara.h index 970de56..f20280d 100644 --- a/src/lara.h +++ b/src/lara.h @@ -405,7 +405,7 @@ struct Lara : Character { if (level->extra.braid > -1) braid = new Braid(this, vec3(-4.0f, 24.0f, -48.0f)); - //reset(15, vec3(70067, -256, 29104), -0.68f); // level 2 (pool) + reset(15, vec3(70067, -256, 29104), -0.68f); // level 2 (pool) #ifdef _DEBUG //reset(14, vec3(40448, 3584, 60928), PI * 0.5f, true); // gym (pool) diff --git a/src/level.h b/src/level.h index bd854dd..993e71e 100644 --- a/src/level.h +++ b/src/level.h @@ -31,6 +31,7 @@ const char GUI[] = #define FOG_DIST (18 * 1024) #define WATER_FOG_DIST (8 * 1024) +//#define WATER_USE_GRID struct Level : IGame { TR::Level level; @@ -89,6 +90,9 @@ struct Level : IGame { for (int type = Shader::WATER_DROP; type <= Shader::WATER_COMPOSE; type++) { sprintf(def, "%s#define WATER_%s\n#define WATER_FOG_DIST (1.0/%d.0)\n", ext, typeNames[type], WATER_FOG_DIST); + #ifdef WATER_USE_GRID + strcat(def, "#define WATER_USE_GRID %d\n"); + #endif shaders[getIndex(Core::passWater, Shader::Type(type), false)] = new Shader(WATER, def); } } @@ -327,7 +331,7 @@ struct Level : IGame { size = vec3(float((maxX - minX) * 512), 1.0f, float((maxZ - minZ) * 512)); // half size pos = vec3(r.info.x + minX * 1024 + size.x, float(posY), r.info.z + minZ * 1024 + size.z); - data[0] = new Texture(nextPow2(w * 64), nextPow2(h * 64), Texture::RGBA_HALF, false); + data[0] = new Texture(w * 64, h * 64, Texture::RGBA_HALF, false); data[1] = new Texture(data[0]->width, data[0]->height, Texture::RGBA_HALF, false); caustics = new Texture(512, 512, Texture::RGB16, false); blank = false; @@ -458,6 +462,8 @@ struct Level : IGame { if (!dropCount) return; level->setShader(Core::passWater, Shader::WATER_DROP, false); + Core::active.shader->setParam(uTexParam, vec4(1.0f / item.data[0]->width, 1.0f / item.data[0]->height, 1.0f, 1.0f)); + vec3 rPosScale[2] = { vec3(0.0f), vec3(1.0f) }; Core::active.shader->setParam(uPosScale, rPosScale[0], 2); @@ -482,6 +488,7 @@ struct Level : IGame { if (item.timer < SIMULATE_TIMESTEP) return; level->setShader(Core::passWater, Shader::WATER_STEP, false); + Core::active.shader->setParam(uTexParam, vec4(1.0f / item.data[0]->width, 1.0f / item.data[0]->height, 1.0f, 1.0f)); Core::active.shader->setParam(uParam, vec4(0.995f, 1.0f, 0, 0)); while (item.timer >= SIMULATE_TIMESTEP) { @@ -522,7 +529,6 @@ struct Level : IGame { level->setShader(Core::passWater, Shader::WATER_MASK, false); Core::active.shader->setParam(uTexParam, vec4(1.0f)); - Core::setBlending(bmNone); Core::setCulling(cfNone); Core::setDepthWrite(false); Core::setColorWrite(false, false, false, true); @@ -543,7 +549,7 @@ struct Level : IGame { // get refraction texture if (!refract || Core::width > refract->width || Core::height > refract->height) { delete refract; - refract = new Texture(nextPow2(Core::width), nextPow2(Core::height), Texture::RGBA, false); + refract = new Texture(Core::width, Core::height, Texture::RGB16, false); } Core::copyTarget(refract, 0, 0, 0, 0, Core::width, Core::height); // copy framebuffer into refraction texture @@ -583,7 +589,6 @@ struct Level : IGame { item.mask->bind(sMask); if (item.timer >= SIMULATE_TIMESTEP || dropCount) { - Core::active.shader->setParam(uTexParam, vec4(1.0f / item.data[0]->width, 1.0f / item.data[0]->height, 1.0f, 1.0f)); // add water drops drop(item); // simulation step @@ -603,7 +608,6 @@ struct Level : IGame { level->setShader(Core::passWater, Shader::WATER_COMPOSE, false); Core::active.shader->setParam(uViewProj, Core::mViewProj); - Core::active.shader->setParam(uPosScale, item.pos, 2); Core::active.shader->setParam(uViewPos, Core::viewPos); Core::active.shader->setParam(uLightPos, Core::lightPos[0], 1); Core::active.shader->setParam(uLightColor, Core::lightColor[0], 1); @@ -613,19 +617,20 @@ struct Level : IGame { float sx = item.size.x * DETAIL / (item.data[0]->width / 2); float sz = item.size.z * DETAIL / (item.data[0]->height / 2); - Core::active.shader->setParam(uTexParam, vec4(1.0f, 1.0f, sx, sz)); + Core::active.shader->setParam(uTexParam, vec4(1.0f, 1.0f, sx, sz)); refract->bind(sDiffuse); reflect->bind(sReflect); item.data[0]->bind(sNormal); Core::setCulling(cfNone); - vec3 rPosScale[2] = { item.pos, item.size * vec3(1.0f / PLANE_DETAIL, 512.0f, 1.0f / PLANE_DETAIL) }; - Core::active.shader->setParam(uPosScale, rPosScale[0], 2); - // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - //level->mesh->renderQuad(); - level->mesh->renderPlane(); - // glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - + #ifdef WATER_USE_GRID + vec3 rPosScale[2] = { item.pos, item.size * vec3(1.0f / PLANE_DETAIL, 512.0f, 1.0f / PLANE_DETAIL) }; + Core::active.shader->setParam(uPosScale, rPosScale[0], 2); + level->mesh->renderPlane(); + #else + Core::active.shader->setParam(uPosScale, item.pos, 2); + level->mesh->renderQuad(); + #endif Core::setCulling(cfFront); } dropCount = 0; @@ -1150,6 +1155,7 @@ struct Level : IGame { Shader::Type type = isModel ? Shader::ENTITY : Shader::SPRITE; if (entity.type == TR::Entity::CRYSTAL) type = Shader::MIRROR; + setRoomParams(entity.room, isModel ? controller->specular : intensityf(lum), type); if (isModel) { // model @@ -1197,9 +1203,6 @@ struct Level : IGame { waterCache->update(); } - - - void setup() { PROFILE_MARKER("SETUP"); @@ -1326,7 +1329,6 @@ struct Level : IGame { Core::pass = Core::passShadow; if (!setupLightCamera()) return; shadow->unbind(sShadow); - Core::setBlending(bmNone); bool colorShadow = shadow->format == Texture::Format::RGBA ? true : false; if (colorShadow) glClearColor(1.0f, 1.0f, 1.0f, 1.0f); @@ -1343,12 +1345,10 @@ struct Level : IGame { PROFILE_MARKER("PASS_COMPOSE"); Core::pass = Core::passCompose; - Core::setBlending(bmAlpha); Core::setDepthTest(true); Core::setDepthWrite(true); shadow->bind(sShadow); renderScene(roomIndex); - Core::setBlending(bmNone); } void render() { @@ -1443,7 +1443,7 @@ struct Level : IGame { Core::setDepthTest(true); - + /* glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); @@ -1452,7 +1452,10 @@ struct Level : IGame { glLoadIdentity(); glOrtho(0, Core::width, 0, Core::height, 0, 1); - shadow->bind(sDiffuse); + if (waterCache->count) + waterCache->refract->bind(sDiffuse); + else + atlas->bind(sDiffuse); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); @@ -1478,7 +1481,7 @@ struct Level : IGame { glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); - + */ Core::setBlending(bmAlpha); // Debug::Level::rooms(level, lara->pos, lara->getEntity().room); diff --git a/src/platform/android/.classpath b/src/platform/android/.classpath new file mode 100644 index 0000000..5176974 --- /dev/null +++ b/src/platform/android/.classpath @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/platform/android/.cproject b/src/platform/android/.cproject new file mode 100644 index 0000000..ad856f2 --- /dev/null +++ b/src/platform/android/.cproject @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/platform/android/.project b/src/platform/android/.project new file mode 100644 index 0000000..e42855c --- /dev/null +++ b/src/platform/android/.project @@ -0,0 +1,49 @@ + + + OpenLara + + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + + diff --git a/src/platform/android/.settings/org.eclipse.cdt.codan.core.prefs b/src/platform/android/.settings/org.eclipse.cdt.codan.core.prefs new file mode 100644 index 0000000..4cc82b1 --- /dev/null +++ b/src/platform/android/.settings/org.eclipse.cdt.codan.core.prefs @@ -0,0 +1,72 @@ +eclipse.preferences.version=1 +org.eclipse.cdt.codan.checkers.errnoreturn=-Warning +org.eclipse.cdt.codan.checkers.errnoreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},implicit\=>false} +org.eclipse.cdt.codan.checkers.errreturnvalue=-Error +org.eclipse.cdt.codan.checkers.errreturnvalue.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.checkers.nocommentinside=-Error +org.eclipse.cdt.codan.checkers.nocommentinside.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.checkers.nolinecomment=-Error +org.eclipse.cdt.codan.checkers.nolinecomment.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.checkers.noreturn=-Error +org.eclipse.cdt.codan.checkers.noreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},implicit\=>false} +org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation=-Error +org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem=-Error +org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem=-Error +org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},no_break_comment\=>"no break",last_case_param\=>false,empty_case_param\=>false} +org.eclipse.cdt.codan.internal.checkers.CatchByReference=-Warning +org.eclipse.cdt.codan.internal.checkers.CatchByReference.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},unknown\=>false,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem=-Error +org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization=-Warning +org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},skip\=>true} +org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.InvalidArguments=-Error +org.eclipse.cdt.codan.internal.checkers.InvalidArguments.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem=-Error +org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem=-Error +org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem=-Error +org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker=-Info +org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},pattern\=>"^[a-z]",macro\=>true,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.OverloadProblem=-Error +org.eclipse.cdt.codan.internal.checkers.OverloadProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem=-Error +org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},paramNot\=>false} +org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},else\=>false,afterelse\=>false} +org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true} +org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true} +org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true,exceptions\=>("@(\#)","$Id")} +org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem=-Error +org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}} +useParentScope=false diff --git a/src/platform/android/.settings/org.eclipse.jdt.core.prefs b/src/platform/android/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..b080d2d --- /dev/null +++ b/src/platform/android/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 +org.eclipse.jdt.core.compiler.compliance=1.6 +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/src/platform/android/AndroidManifest.xml b/src/platform/android/AndroidManifest.xml new file mode 100644 index 0000000..9061603 --- /dev/null +++ b/src/platform/android/AndroidManifest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/platform/android/jni/Android.mk b/src/platform/android/jni/Android.mk new file mode 100644 index 0000000..2ccfd8e --- /dev/null +++ b/src/platform/android/jni/Android.mk @@ -0,0 +1,17 @@ +LOCAL_PATH := $(call my-dir) +SRC := ${LOCAL_PATH}/../../../../ + +include $(CLEAR_VARS) + +#-ffunction-sections -fdata-sections #-frtti #-fexceptions # + +LOCAL_CFLAGS := -DANDROID -fvisibility=hidden +LOCAL_LDFLAGS := -Wl,--gc-sections +LOCAL_MODULE := game +LOCAL_SRC_FILES := main.cpp\ + $(SRC)libs/stb_vorbis/stb_vorbis.c\ + $(SRC)libs/minimp3/minimp3.cpp +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../ +LOCAL_LDLIBS := -lGLESv2 -llog + +include $(BUILD_SHARED_LIBRARY) \ No newline at end of file diff --git a/src/platform/android/jni/Application.mk b/src/platform/android/jni/Application.mk new file mode 100644 index 0000000..61a1d43 --- /dev/null +++ b/src/platform/android/jni/Application.mk @@ -0,0 +1,8 @@ +¹NDK_TOOLCHAIN_VERSION := 4.8 +NDK_TOOLCHAIN_VERSION := clang +APP_ABI := armeabi-v7a +# Enable C++11. However, pthread, rtti and exceptions aren’t enabled +APP_CPPFLAGS += -std=c++11 +# Instruct to use the static GNU STL implementation +#APP_STL := gnustl_static +LOCAL_C_INCLUDES += ${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.8/include \ No newline at end of file diff --git a/src/platform/android/jni/main.cpp b/src/platform/android/jni/main.cpp new file mode 100644 index 0000000..ab167fc --- /dev/null +++ b/src/platform/android/jni/main.cpp @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include + +#include "game.h" + +int getTime() { + timeval time; + gettimeofday(&time, NULL); + return (time.tv_sec * 1000) + (time.tv_usec / 1000); +} + +extern "C" { + +int lastTime, fpsTime, fps; + +JNIEXPORT void JNICALL Java_org_xproger_openlara_Wrapper_nativeInit(JNIEnv* env, jobject obj, jstring packName, jint levelOffset, jint musicOffset) { + LOG("native init"); + const char* pack = env->GetStringUTFChars(packName, NULL); + Stream *level = new Stream(pack); + Stream *music = new Stream(pack); + level->seek(levelOffset); + music->seek(musicOffset); + env->ReleaseStringUTFChars(packName, pack); + LOG("game init"); + + Game::init(level, music); + LOG("done"); + + lastTime = getTime(); + fpsTime = lastTime + 1000; + fps = 0; + LOG("init"); +} + +JNIEXPORT void JNICALL Java_org_xproger_openlara_Wrapper_nativeFree(JNIEnv* env) { + Game::free(); +} + +JNIEXPORT void JNICALL Java_org_xproger_openlara_Wrapper_nativeReset(JNIEnv* env) { +// core->reset(); +} + +JNIEXPORT void JNICALL Java_org_xproger_openlara_Wrapper_nativeUpdate(JNIEnv* env) { + int time = getTime(); + if (time == lastTime) + return; + + float delta = min(1.0f, (time - lastTime) * 0.001f); + while (delta > EPS) { + Core::deltaTime = min(delta, 1.0f / 30.0f); + Game::update(); + delta -= Core::deltaTime; + } + lastTime = time; +} + +JNIEXPORT void JNICALL Java_org_xproger_openlara_Wrapper_nativeRender(JNIEnv* env) { + Core::stats.dips = 0; + Core::stats.tris = 0; + Game::render(); + if (fpsTime < getTime()) { + LOG("FPS: %d DIP: %d TRI: %d\n", fps, Core::stats.dips, Core::stats.tris); + fps = 0; + fpsTime = getTime() + 1000; + } else + fps++; +} + +JNIEXPORT void JNICALL Java_org_xproger_openlara_Wrapper_nativeResize(JNIEnv* env, jobject obj, jint w, jint h) { + Core::width = w; + Core::height = h; +} + +float DeadZone(float x) { + return x = fabsf(x) < 0.2f ? 0.0f : x; +} + +JNIEXPORT void JNICALL Java_org_xproger_openlara_Wrapper_nativeTouch(JNIEnv* env, jobject obj, jint id, jint state, jfloat x, jfloat y) { + if (id > 1) return; +/* gamepad + if (state < 0) { + state = -state; + Input::Joystick &joy = Input::joy; + + switch (state) { + case 3 : + joy.L.x = DeadZone(x); + joy.L.y = DeadZone(y); + break; + case 4 : + joy.R.x = DeadZone(x); + joy.R.y = DeadZone(y); + break; + default: + joy.down[(int)x] = state != 1; + } + return; + } +*/ + if (state == 3 && x < Core::width / 2) { + vec2 center(Core::width * 0.25f, Core::height * 0.6f); + vec2 pos(x, y); + vec2 d = pos - center; + + float angle = atan2(d.x, -d.y) + PI * 0.125f; + if (angle < 0.0f) angle += PI2; + int pov = int(angle / (PI * 0.25f)); + + Input::setPos(ikJoyPOV, vec2(float(1 + pov), 0.0f)); + }; + + if (state == 2 && x > Core::width / 2) { + int i = y / (Core::height / 3); + InputKey key; + if (i == 0) + key = ikJoyX; + else if (i == 1) + key = ikJoyA; + else + key = ikJoyY; + Input::setDown(key, true); + } + + if (state == 1) { + Input::setPos(ikJoyPOV, vec2(0.0f)); + Input::setDown(ikJoyA, false); + Input::setDown(ikJoyX, false); + Input::setDown(ikJoyY, false); + } +} + +JNIEXPORT void JNICALL Java_org_xproger_openlara_Wrapper_nativeSoundFill(JNIEnv* env, jobject obj, jshortArray buffer) { + jshort *frames = env->GetShortArrayElements(buffer, NULL); + jsize count = env->GetArrayLength(buffer) / 2; + Sound::fill((Sound::Frame*)frames, count); + env->ReleaseShortArrayElements(buffer, frames, 0); +} + +} + + diff --git a/src/platform/android/lint.xml b/src/platform/android/lint.xml new file mode 100644 index 0000000..d8916ff --- /dev/null +++ b/src/platform/android/lint.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/platform/android/proguard-project.txt b/src/platform/android/proguard-project.txt new file mode 100644 index 0000000..515bc2d --- /dev/null +++ b/src/platform/android/proguard-project.txt @@ -0,0 +1,144 @@ +# To enable ProGuard in your project, edit project.properties +# to define the proguard.config property as described in that file. +# +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in ${sdk.dir}/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the ProGuard +# include property in project.properties. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +#Use 5 step of optimization +-optimizationpasses 5 + +#When not preverifing in a case-insensitive filing system, such as Windows. This tool will unpack your processed jars,(if using windows you should then use): +-dontusemixedcaseclassnames + +#Specifies not to ignore non-public library classes. As of version 4.5, this is the default setting +-dontskipnonpubliclibraryclasses + +# Optimization is turned off by default. Dex does not like code run +# through the ProGuard optimize and preverify steps (and performs some +# of these optimizations on its own). +-dontoptimize +-dontpreverify + +-dontwarn android.support.** + +#Specifies to write out some more information during processing. If the program terminates with an exception, this option will print out the entire stack trace, instead of just the exception message. +-verbose + +#The -optimizations option disables some arithmetic simplifications that Dalvik 1.0 and 1.5 can't handle. Note that the Dalvik VM also can't handle aggressive overloading (of static fields). +#To understand or change this check http://proguard.sourceforge.net/index.html#/manual/optimizations.html +#-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +# Note that if you want to enable optimization, you cannot just +# include optimization flags in your own project configuration file; +# instead you will need to point to the +# "proguard-android-optimize.txt" file instead of this one from your +# project.properties file. + +#To repackage classes on a single package +#-repackageclasses '' + +#Uncomment if using annotations to keep them. +#-keepattributes *Annotation* + +#Keep classes that are referenced on the AndroidManifest +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +#-keep public class * extends android.app.Service +#-keep public class * extends android.content.BroadcastReceiver +#-keep public class * extends android.content.ContentProvider +#-keep public class * extends android.app.backup.BackupAgentHelper +#-keep public class * extends android.preference.Preference +#-keep public class com.google.vending.licensing.ILicensingService +#-keep public class com.android.vending.licensing.ILicensingService +#Compatibility library +#-keep public class * extends android.support.v4.app.Fragment +#-keep public class * extends android.app.Fragment + +#To maintain custom components names that are used on layouts XML. +#Uncomment if having any problem with the approach below +#-keep public class custom.components.package.and.name.** + +# keep setters in Views so that animations can still work. +# see http://proguard.sourceforge.net/manual/examples.html#beans + -keepclassmembers public class * extends android.view.View { + void set*(***); + *** get*(); +} + +#To remove debug logs: +-assumenosideeffects class android.util.Log { + public static *** d(...); + public static *** v(...); + public static *** w(...); +} + +#To avoid changing names of methods invoked on layout's onClick. +# Uncomment and add specific method names if using onClick on layouts +#-keepclassmembers class * { +# public void onClickButton(android.view.View); +#} + +#Maintain java native methods +-keepclasseswithmembernames class * { + native ; +} + + +#To maintain custom components names that are used on layouts XML: +-keep public class * extends android.view.View { + public (android.content.Context); +} +-keep public class * extends android.view.View { + public (android.content.Context, android.util.AttributeSet); +} +-keep public class * extends android.view.View { + public (android.content.Context, android.util.AttributeSet, int); +} + +#Maintain enums +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +#To keep parcelable classes (to serialize - deserialize objects to sent through Intents) +#-keep class * implements android.os.Parcelable { +# public static final android.os.Parcelable$Creator *; +#} + +#Keep the R +-keepclassmembers class **.R$* { + public static ; +} + +###### ADDITIONAL OPTIONS NOT USED NORMALLY + +#To keep callback calls. Uncomment if using any +#http://proguard.sourceforge.net/index.html#/manual/examples.html#callback +#-keep class mypackage.MyCallbackClass { +# void myCallbackMethod(java.lang.String); +#} + +#Uncomment if using Serializable +#-keepclassmembers class * implements java.io.Serializable { +# private static final java.io.ObjectStreamField[] serialPersistentFields; +# private void writeObject(java.io.ObjectOutputStream); +# private void readObject(java.io.ObjectInputStream); +# java.lang.Object writeReplace(); +# java.lang.Object readResolve(); +#} \ No newline at end of file diff --git a/src/platform/android/project.properties b/src/platform/android/project.properties new file mode 100644 index 0000000..cf667f3 --- /dev/null +++ b/src/platform/android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-14 diff --git a/src/platform/android/res/drawable-hdpi/ic_launcher.png b/src/platform/android/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..96a442e5b8e9394ccf50bab9988cb2316026245d GIT binary patch literal 9397 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 literal 0 HcmV?d00001 diff --git a/src/platform/android/res/drawable-xhdpi/ic_launcher.png b/src/platform/android/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..71c6d760f05183ef8a47c614d8d13380c8528499 GIT binary patch literal 14383 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 literal 0 HcmV?d00001 diff --git a/src/platform/android/res/values/strings.xml b/src/platform/android/res/values/strings.xml new file mode 100644 index 0000000..10621b5 --- /dev/null +++ b/src/platform/android/res/values/strings.xml @@ -0,0 +1,5 @@ + + + OpenLara + + diff --git a/src/platform/android/src/org/xproger/openlara/MainActivity.java b/src/platform/android/src/org/xproger/openlara/MainActivity.java new file mode 100644 index 0000000..438e88e --- /dev/null +++ b/src/platform/android/src/org/xproger/openlara/MainActivity.java @@ -0,0 +1,306 @@ +package org.xproger.openlara; + +import java.util.ArrayList; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioTrack; +import android.opengl.GLSurfaceView; +import android.opengl.GLSurfaceView.Renderer; +import android.os.Bundle; +import android.app.Activity; +import android.content.res.AssetFileDescriptor; +import android.util.Log; +import android.util.SparseIntArray; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnGenericMotionListener; +import android.view.View.OnKeyListener; +import android.view.View.OnTouchListener; +import android.view.Window; +import android.view.WindowManager; + +public class MainActivity extends Activity implements OnTouchListener, OnGenericMotionListener, OnKeyListener { + private GLSurfaceView view; + private Wrapper wrapper; + private SparseIntArray joys = new SparseIntArray(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | + WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD | + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | + WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); + + super.onCreate(savedInstanceState); + + view = new GLSurfaceView(this); + view.setEGLConfigChooser(5, 6, 5, 0, 16, 0); + view.setEGLContextClientVersion(2); + view.setRenderer(wrapper = new Wrapper()); + + view.setFocusable(true); + view.setFocusableInTouchMode(true); + + view.setOnTouchListener(this); + view.setOnGenericMotionListener(this); + view.setOnKeyListener(this); + + setContentView(view); + try { + String packName = getPackageManager().getPackageInfo(getPackageName(), 1).applicationInfo.sourceDir; + AssetFileDescriptor fLevel = this.getResources().openRawResourceFd(R.raw.level2); + AssetFileDescriptor fMusic = this.getResources().openRawResourceFd(R.raw.music); + + wrapper.onCreate(packName, (int)fLevel.getStartOffset(), (int)fMusic.getStartOffset()); + } catch (Exception e) { + e.printStackTrace(); + finish(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + wrapper.onDestroy(); + } + + @Override + protected void onPause() { + super.onPause(); + wrapper.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + wrapper.onResume(); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + int action = event.getAction(); + int type = action & MotionEvent.ACTION_MASK; + int state; + + switch (type) { + case MotionEvent.ACTION_DOWN : + case MotionEvent.ACTION_UP : + case MotionEvent.ACTION_MOVE : + state = type == MotionEvent.ACTION_MOVE ? 3 : (type == MotionEvent.ACTION_DOWN ? 2 : 1); + for (int i = 0; i < event.getPointerCount(); i++) + wrapper.onTouch(event.getPointerId(i), state, event.getX(i), event.getY(i)); + break; + case MotionEvent.ACTION_POINTER_DOWN : + case MotionEvent.ACTION_POINTER_UP : + int i = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + state = type == MotionEvent.ACTION_POINTER_DOWN ? 2 : 1; + wrapper.onTouch(event.getPointerId(i), state, event.getX(i), event.getY(i)); + break; + } + return true; + } + + private int getJoyIndex(InputDevice dev) { + int src = dev.getSources(); + if ((src & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD || + (src & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK) { + + int id = dev.getId(); + int index = joys.get(id, -1); + + if (index == -1) { + index = joys.size(); + joys.append(id, index); + } + return index; + } + return -1; + } + + @Override + public boolean onGenericMotion(View v, MotionEvent event) { + int index = getJoyIndex(event.getDevice()); + if (index == -1) return false; + + wrapper.onTouch(index, -3, event.getAxisValue(MotionEvent.AXIS_X), + event.getAxisValue(MotionEvent.AXIS_Y)); + + wrapper.onTouch(index, -4, event.getAxisValue(MotionEvent.AXIS_Z), + event.getAxisValue(MotionEvent.AXIS_RZ)); + + return true; + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + int index = getJoyIndex(event.getDevice()); + if (index == -1) return false; + + int btn; + + switch (keyCode) { + case KeyEvent.KEYCODE_BUTTON_A : btn = 0; break; + case KeyEvent.KEYCODE_BUTTON_B : btn = 1; break; + case KeyEvent.KEYCODE_BUTTON_X : btn = 2; break; + case KeyEvent.KEYCODE_BUTTON_Y : btn = 3; break; + default : btn = -1; + } + + if (btn != -1) { + wrapper.onTouch(index, event.getAction() == KeyEvent.ACTION_DOWN ? -2 : -1, btn, 0); + return true; + } + return false; + } + + static { + System.loadLibrary("game"); + } +} + +class Sound { + private short buffer[]; + private static AudioTrack audioTrack; + + public void start(final Wrapper wrapper) { + int bufferSize = AudioTrack.getMinBufferSize(22050, AudioFormat.CHANNEL_CONFIGURATION_STEREO, AudioFormat.ENCODING_PCM_16BIT); + System.out.println(String.format("sound buffer size: %d", bufferSize)); + + buffer = new short [bufferSize / 2]; + audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, + 44100, + AudioFormat.CHANNEL_CONFIGURATION_STEREO, + AudioFormat.ENCODING_PCM_16BIT, + bufferSize, + AudioTrack.MODE_STREAM); + audioTrack.play(); + + new Thread( new Runnable() { + public void run() { + while ( audioTrack.getPlayState() != AudioTrack.PLAYSTATE_STOPPED ) { + if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING && wrapper.ready) { + synchronized (wrapper) { + Wrapper.nativeSoundFill(buffer); + } + audioTrack.write(buffer, 0, buffer.length); + audioTrack.flush(); + } else + try { + Thread.sleep(100); + } catch(Exception e) { + // + }; + } + } + } ).start(); + } + + public void stop() { + audioTrack.flush(); + audioTrack.stop(); + audioTrack.release(); + } + + public void play() { + audioTrack.play(); + } + + public void pause() { + audioTrack.pause(); + } +} + +class Touch { + int id, state; + float x, y; + public Touch(int _id, int _state, float _x, float _y) { + id = _id; + state = _state; + x = _x; + y = _y; + }; +} + +class Wrapper implements Renderer { + public static native void nativeInit(String packName, int levelOffset, int musicOffset); + public static native void nativeFree(); + public static native void nativeReset(); + public static native void nativeResize(int w, int h); + public static native void nativeUpdate(); + public static native void nativeRender(); + public static native void nativeTouch(int id, int state, float x, float y); + public static native void nativeSoundFill(short buffer[]); + + public Boolean ready = false; + private String packName; + private int levelOffset; + private int musicOffset; + private ArrayList touch = new ArrayList(); + private Sound sound; + + public void onCreate(String packName, int levelOffset, int musicOffset) { + this.packName = packName; + this.levelOffset = levelOffset; + this.musicOffset = musicOffset; + + sound = new Sound(); + sound.start(this); + } + + public void onDestroy() { + sound.stop(); + nativeFree(); + } + + public void onPause() { + sound.pause(); + } + + public void onResume() { + sound.play(); + if (ready) nativeReset(); + } + + public void onTouch(int id, int state, float x, float y) { + synchronized (this) { + touch.add(new Touch(id, state, x, y)); + } + } + + @Override + public void onDrawFrame(GL10 gl) { + synchronized (this) { + for (int i = 0; i < touch.size(); i++) { + Touch t = touch.get(i); + nativeTouch(t.id, t.state, t.x, t.y); + } + touch.clear(); + nativeUpdate(); + } + nativeRender(); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + nativeResize(width, height); + } + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + if (!ready) { + nativeInit(packName, levelOffset, musicOffset); + sound.play(); + ready = true; + } + } +} diff --git a/src/platform/web/build.bat b/src/platform/web/build.bat index 91ab70e..28c84f5 100644 --- a/src/platform/web/build.bat +++ b/src/platform/web/build.bat @@ -6,4 +6,4 @@ set FLAGS=-O3 -Wno-deprecated-register --llvm-opts 2 -fmax-type-align=2 -std=c++ set PRELOAD=./LEVEL2.PSX echo. call em++ %SRC% %FLAGS% -o %PROJ%.js --preload-file %PRELOAD% -gzip.exe -9 -f %PROJ%.data %PROJ%.js %PROJ%.js.mem \ No newline at end of file +ggzip.exe -9 -f %PROJ%.data %PROJ%.js %PROJ%.js.mem \ No newline at end of file diff --git a/src/shader.glsl b/src/shader.glsl index 7665e12..7195aa2 100644 --- a/src/shader.glsl +++ b/src/shader.glsl @@ -213,7 +213,11 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - caustics coords vec3 lv = uLightPos[0].xyz - vCoord.xyz; float fade = clamp(dot(lv, lv) / uLightColor[0].w, 0.0, 1.0); - return clamp(mix(rShadow, 1.0, fade), -lightProj.w, 1.0); + return mix(rShadow, 1.0, fade); + } + + float getShadow() { + return min(dot(vNormal.xyz, uLightPos[0].xyz - vCoord), vLightProj.w) > 0.0 ? getShadow(vLightProj) : 1.0; } vec3 calcLight(vec3 normal, vec3 pos, vec4 color) { @@ -272,16 +276,19 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - caustics coords void main() { #ifndef PASS_SHADOW #ifndef PASS_AMBIENT - if (vCoord.y * uParam.z > uParam.w) - discard; + #if defined(TYPE_ENTITY) && defined(CAUSTICS) + if (vCoord.y * uParam.z > uParam.w) + discard; + #endif #endif #endif vec4 color = texture2D(sDiffuse, vTexCoord.xy); -// if (color.w <= 0.6) { -// discard; -// } + #ifdef ALPHA_TEST + if (color.w <= 0.6) + discard; + #endif #ifdef PASS_SHADOW #ifdef SHADOW_COLOR @@ -320,26 +327,20 @@ varying vec4 vTexCoord; // xy - atlas coords, zw - caustics coords #endif #ifdef TYPE_ROOM - float rShadow = dot(normal, uLightPos[0].xyz - vCoord) > 0.0 ? getShadow(vLightProj) : 1.0; - //light += calcLight(normal, uLightPos[0], uLightColor[0]); - light += mix(min(uColor.w, vColor.w), vColor.w, rShadow); + light += mix(min(uColor.w, vColor.w), vColor.w, getShadow()); #ifdef CAUSTICS light += calcCaustics(normal); #endif -//color.xyz = vec3(rShadow); -//light.xyz = vec3(1.0); #endif #ifdef TYPE_ENTITY vec3 rAmbient = calcAmbient(normal); - float rShadow = getShadow(vLightProj); + float rShadow = getShadow(); light += calcLight(normal, uLightPos[0], uLightColor[0]) * rShadow + rAmbient; color.xyz += calcSpecular(normal, viewVec, uLightPos[0], uLightColor[0], uColor.w * rShadow + 0.03); #ifdef CAUSTICS light += calcCaustics(normal); #endif -//color.xyz = vec3(rShadow); -//light.xyz = vec3(1.0); #endif #ifdef TYPE_MIRROR diff --git a/src/texture.h b/src/texture.h index 0028132..769dfb2 100644 --- a/src/texture.h +++ b/src/texture.h @@ -11,7 +11,14 @@ struct Texture { Format format; bool cube; - Texture(int width, int height, Format format, bool cube, void *data = NULL, bool filter = true) : width(width), height(height), cube(cube) { + Texture(int width, int height, Format format, bool cube, void *data = NULL, bool filter = true) : cube(cube) { + if (!Core::support.texNPOT) { + width = nextPow2(width); + height = nextPow2(height); + } + this->width = width; + this->height = height; + glGenTextures(1, &ID); bind(0); diff --git a/src/utils.h b/src/utils.h index 9942961..e78b62f 100644 --- a/src/utils.h +++ b/src/utils.h @@ -733,7 +733,7 @@ struct Stream { #else f = fopen(name, "rb"); #endif - if (!f) LOG("error loading file\n"); + if (!f) LOG("error loading file \"%s\"\n", name); fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); diff --git a/src/water.glsl b/src/water.glsl index 48d2d55..db0d347 100644 --- a/src/water.glsl +++ b/src/water.glsl @@ -11,13 +11,7 @@ varying vec4 vOldPos; varying vec4 vNewPos; varying vec3 vViewVec; varying vec3 vLightVec; -/* -#define WATER_DROP 0 -#define WATER_STEP 1 -#define WATER_CAUSTICS 2 -#define WATER_MASK 3 -#define WATER_COMPOSE 4 -*/ + uniform vec3 uViewPos; uniform mat4 uViewProj; uniform vec3 uLightPos; @@ -43,8 +37,10 @@ uniform sampler2D sNormal; float height = 0.0; #ifdef WATER_COMPOSE - vTexCoord = (aCoord.xy * (1.0 / 48.0) * 0.5 + 0.5) * uTexParam.zw; - height = texture2D(sNormal, vTexCoord).x; + #ifdef WATER_USE_GRID + vTexCoord = (aCoord.xy * (1.0 / 48.0) * 0.5 + 0.5) * uTexParam.zw; + height = texture2D(sNormal, vTexCoord).x; + #endif #endif vCoord = vec3(aCoord.x, height, aCoord.y) * uPosScale[1] + uPosScale[0]; @@ -177,29 +173,28 @@ uniform sampler2D sNormal; return color; } - vec4 pass() { - return + vec4 pass() { #ifdef WATER_DROP - drop(); + return drop(); #endif #ifdef WATER_STEP - calc(); + return calc(); #endif #ifdef WATER_CAUSTICS - caustics(); + return caustics(); #endif #ifdef WATER_MASK - mask(); + return mask(); #endif #ifdef WATER_COMPOSE - compose(); + return compose(); #endif - vec4(1.0, 0.0, 0.0, 1.0); + return vec4(1.0, 0.0, 1.0, 1.0); } void main() {