diff --git a/CHANGES.md b/CHANGES.md index d98393a03..a1c12e1cc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ ## Change logs -**1.1.0harmattan36(natasha)** - **2023-12-31** + +> 1.1.0harmattan37 (2024-01-06) + +* Fixed on-screen buttons initial keycodes. +* On-screen slider button can setup clickable. +* Add dds screenshot support. +* Add cvar `r_scaleMenusTo43` for 4:3 menu. + +---------------------------------------------------------------------------------- + +> 1.1.0harmattan36 (2023-12-31) * Fixed prelight shadow's shadow mapping. * Fixed EFX Reverb in Quake4. @@ -12,11 +22,11 @@ * Add `Stupid Angry Bot`(a7x) mod of DOOM3 support(need DOOM3: RoE game data), game data directory named `sabot`. More view in [SABot(a7x)](https://www.moddb.com/downloads/sabot-alpha-7x). * Add `Overthinked DooM^3` mod of DOOM3 support, game data directory named `overthinked`. More view in [Overthinked DooM^3](https://www.moddb.com/mods/overthinked-doom3). * Add `Fragging Free` mod of DOOM3 support(need DOOM3: RoE game data), game data directory named `fraggingfree`. More view in [Fragging Free](https://www.moddb.com/mods/fragging-free). -* Add `HeXen:Edge of Chaos` mod of DOOM3 support, game data directory named `hexeneoc`. More view in [Overthinked DooM^3](https://www.moddb.com/mods/hexen-edge-of-chaos). +* Add `HeXen:Edge of Chaos` mod of DOOM3 support, game data directory named `hexeneoc`. More view in [HeXen:Edge of Chaos](https://www.moddb.com/mods/hexen-edge-of-chaos). ---------------------------------------------------------------------------------- -> 1.1.0harmattan33 (2023-10-29) +> 1.1.0harmattan35 (2023-10-29) * Optimize soft shadow with shadow mapping. Add shadow map with depth texture in OpenGLES2.0. * Add OpenAL(soft) and EFX Reverb support. diff --git a/CHANGES.zh.md b/CHANGES.zh.md index 6fc38e38a..0051eee9e 100644 --- a/CHANGES.zh.md +++ b/CHANGES.zh.md @@ -1,5 +1,15 @@ ## 更新日志 -**1.1.0harmattan36(natasha)** - **2023-12-31** + +> 1.1.0harmattan37 (2024-01-06) + +* 修复屏幕按键初始键码配置. +* 滑动按键支持设置为点击触发. +* 新增dds格式屏幕截图. +* 新增cvar `r_scaleMenusTo43`启用4:3比例菜单. + +---------------------------------------------------------------------------------- + +> 1.1.0harmattan36 (2023-12-31) * 修复预烘培阴影图软阴影渲染. * 雷神之锤4修复EFX混响. @@ -12,11 +22,11 @@ * 添加毁灭战士3 mod `Stupid Angry Bot`(a7x)(需要邪恶复苏数据包), 游戏数据文件夹名为`sabot`. 详情 [SABot(a7x)](https://www.moddb.com/downloads/sabot-alpha-7x). * 添加毁灭战士3 mod `Overthinked DooM^3`, 游戏数据文件夹名为`overthinked`. 详情 [Overthinked DooM^3](https://www.moddb.com/mods/overthinked-doom3). * 添加毁灭战士3 mod `Fragging Free`(需要邪恶复苏数据包), 游戏数据文件夹名为`fraggingfree`. 详情 [Fragging Free](https://www.moddb.com/mods/fragging-free). -* 添加毁灭战士3 mod `HeXen:Edge of Chaos`, 游戏数据文件夹名为`hexeneoc`. 详情 [Overthinked DooM^3](https://www.moddb.com/mods/hexen-edge-of-chaos). +* 添加毁灭战士3 mod `HeXen:Edge of Chaos`, 游戏数据文件夹名为`hexeneoc`. 详情 [HeXen:Edge of Chaos](https://www.moddb.com/mods/hexen-edge-of-chaos). ---------------------------------------------------------------------------------- -> 1.1.0harmattan33 (2023-10-29) +> 1.1.0harmattan35 (2023-10-29) * 优化Shadow mapping软阴影. OpenGLES2.0阴影图使用深度纹理. * 新增OpenALA(soft)和EFX混响支持.", diff --git a/CHECK_FOR_UPDATE.json b/CHECK_FOR_UPDATE.json index 7b24d6187..8fcde56e2 100644 --- a/CHECK_FOR_UPDATE.json +++ b/CHECK_FOR_UPDATE.json @@ -1,7 +1,7 @@ { - "release":36, - "update":"2023-12-31", - "version":"1.1.0harmattan36", - "apk_url":"https://github.com/glKarin/com.n0n3m4.diii4a/releases/download/v1.1.0harmattan36/idTech4A++_1.1.0harmattan36.apk", - "changes":" * Fixed prelight shadow's shadow mapping.\n * Fixed EFX Reverb in Quake4.\n * Add translucent stencil shadow support in stencil shadow(bool cvar `harm_r_translucentStencilShadow`(default 0); float cvar `harm_r_stencilShadowAlpha` for setting transparency).\n * Add float cvar `harm_ui_subtitlesTextScale` control subtitles's text scale in Prey.\n * Support cvar `r_brightness`.\n * Fixed weapon projectile's scorches decals rendering in Prey(2006).\n * Data directory chooser support Android SAF.\n * New default on-screen buttons layout.\n * Add `Stupid Angry Bot`(a7x) mod of DOOM3 support(need DOOM3: RoE game data), game data directory named `sabot`. More view in [SABot(a7x)](https://www.moddb.com/downloads/sabot-alpha-7x).\n * Add `Overthinked DooM^3` mod of DOOM3 support, game data directory named `overthinked`. More view in [Overthinked DooM^3](https://www.moddb.com/mods/overthinked-doom3).\n * Add `Fragging Free` mod of DOOM3 support(need DOOM3: RoE game data), game data directory named `fraggingfree`. More view in [Fragging Free](https://www.moddb.com/mods/fragging-free).\n * Add `HeXen:Edge of Chaos` mod of DOOM3 support, game data directory named `hexeneoc`. More view in [Overthinked DooM^3](https://www.moddb.com/mods/hexen-edge-of-chaos)." + "release":37, + "update":"2024-01-06", + "version":"1.1.0harmattan37", + "apk_url":"https://github.com/glKarin/com.n0n3m4.diii4a/releases/download/v1.1.0harmattan37/idTech4A++_1.1.0harmattan37.apk", + "changes":" * Fixed on-screen buttons initial keycodes.\n * On-screen slider button can setup clickable.\n * Add dds screenshot support.\n * Add cvar `r_scaleMenusTo43` for 4:3 menu." } diff --git a/Q3E/src/main/java/com/n0n3m4/q3e/Q3EGlobals.java b/Q3E/src/main/java/com/n0n3m4/q3e/Q3EGlobals.java index 7cc19cabf..0b97fe4b0 100644 --- a/Q3E/src/main/java/com/n0n3m4/q3e/Q3EGlobals.java +++ b/Q3E/src/main/java/com/n0n3m4/q3e/Q3EGlobals.java @@ -55,6 +55,8 @@ public final class Q3EGlobals // on-screen slider type public static final int ONSCRREN_SLIDER_STYLE_LEFT_RIGHT = 0; public static final int ONSCRREN_SLIDER_STYLE_DOWN_RIGHT = 1; + public static final int ONSCRREN_SLIDER_STYLE_LEFT_RIGHT_SPLIT_CLICK = 2; + public static final int ONSCRREN_SLIDER_STYLE_DOWN_RIGHT_SPLIT_CLICK = 3; // game state public static final int STATE_NONE = 0; diff --git a/Q3E/src/main/java/com/n0n3m4/q3e/Q3EInterface.java b/Q3E/src/main/java/com/n0n3m4/q3e/Q3EInterface.java index 4fa7eba25..5a88bee87 100644 --- a/Q3E/src/main/java/com/n0n3m4/q3e/Q3EInterface.java +++ b/Q3E/src/main/java/com/n0n3m4/q3e/Q3EInterface.java @@ -22,6 +22,7 @@ import android.content.Context; import android.os.Environment; import android.preference.PreferenceManager; +import android.util.Log; import java.io.File; import java.util.Arrays; @@ -32,6 +33,7 @@ public class Q3EInterface private static int[] _defaultArgs; private static int[] _defaultType; static { + Q3EKeyCodes.InitD3Keycodes(); InitDefaultTypeTable(); InitDefaultArgTable(); } @@ -40,7 +42,7 @@ public class Q3EInterface public String[] defaults_table; public String[] texture_table; public int[] type_table; - public int[] arg_table; //key,key,key,style or key,canbeheld,style,null + public int[] arg_table; // slider: key,key,key,style | button: key,canbeheld,style,null public boolean isRTCW=false; public boolean isQ1=false; @@ -316,79 +318,79 @@ private static void InitDefaultArgTable() int[] arg_table = new int[Q3EGlobals.UI_SIZE * 4]; arg_table[Q3EGlobals.UI_SHOOT * 4] = Q3EKeyCodes.KeyCodes.K_MOUSE1; - arg_table[Q3EGlobals.UI_SHOOT * 4 + 1] = 0; - arg_table[Q3EGlobals.UI_SHOOT * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_SHOOT * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_NOT_HOLD; + arg_table[Q3EGlobals.UI_SHOOT * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_SHOOT * 4 + 3] = 0; arg_table[Q3EGlobals.UI_JUMP * 4] = Q3EKeyCodes.KeyCodes.K_SPACE; - arg_table[Q3EGlobals.UI_JUMP * 4 + 1] = 0; - arg_table[Q3EGlobals.UI_JUMP * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_JUMP * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_NOT_HOLD; + arg_table[Q3EGlobals.UI_JUMP * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_JUMP * 4 + 3] = 0; arg_table[Q3EGlobals.UI_CROUCH * 4] = Q3EKeyCodes.KeyCodesD3.K_C; // BFG - arg_table[Q3EGlobals.UI_CROUCH * 4 + 1] = 1; - arg_table[Q3EGlobals.UI_CROUCH * 4 + 2] = 1; + arg_table[Q3EGlobals.UI_CROUCH * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_CAN_HOLD; + arg_table[Q3EGlobals.UI_CROUCH * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_RIGHT_BOTTOM; arg_table[Q3EGlobals.UI_CROUCH * 4 + 3] = 0; arg_table[Q3EGlobals.UI_RELOADBAR * 4] = Q3EKeyCodes.KeyCodesD3.K_BRACKET_RIGHT; // 93 arg_table[Q3EGlobals.UI_RELOADBAR * 4 + 1] = Q3EKeyCodes.KeyCodesD3.K_R; // 114 arg_table[Q3EGlobals.UI_RELOADBAR * 4 + 2] = Q3EKeyCodes.KeyCodesD3.K_BRACKET_LEFT; // 91 - arg_table[Q3EGlobals.UI_RELOADBAR * 4 + 3] = 0; + arg_table[Q3EGlobals.UI_RELOADBAR * 4 + 3] = Q3EGlobals.ONSCRREN_SLIDER_STYLE_LEFT_RIGHT; arg_table[Q3EGlobals.UI_PDA * 4] = Q3EKeyCodes.KeyCodes.K_TAB; - arg_table[Q3EGlobals.UI_PDA * 4 + 1] = 0; - arg_table[Q3EGlobals.UI_PDA * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_PDA * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_NOT_HOLD; + arg_table[Q3EGlobals.UI_PDA * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_PDA * 4 + 3] = 0; arg_table[Q3EGlobals.UI_FLASHLIGHT * 4] = Q3EKeyCodes.KeyCodesD3.K_F; // BFG - arg_table[Q3EGlobals.UI_FLASHLIGHT * 4 + 1] = 0; - arg_table[Q3EGlobals.UI_FLASHLIGHT * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_FLASHLIGHT * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_NOT_HOLD; + arg_table[Q3EGlobals.UI_FLASHLIGHT * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_FLASHLIGHT * 4 + 3] = 0; arg_table[Q3EGlobals.UI_SAVE * 4] = Q3EKeyCodes.KeyCodes.K_F5; arg_table[Q3EGlobals.UI_SAVE * 4 + 1] = Q3EKeyCodes.KeyCodes.K_ESCAPE; arg_table[Q3EGlobals.UI_SAVE * 4 + 2] = Q3EKeyCodes.KeyCodes.K_F9; - arg_table[Q3EGlobals.UI_SAVE * 4 + 3] = 1; + arg_table[Q3EGlobals.UI_SAVE * 4 + 3] = Q3EGlobals.ONSCRREN_SLIDER_STYLE_DOWN_RIGHT; arg_table[Q3EGlobals.UI_1 * 4] = Q3EKeyCodes.KeyCodesD3BFG.K_1; - arg_table[Q3EGlobals.UI_1 * 4 + 1] = 0; - arg_table[Q3EGlobals.UI_1 * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_1 * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_NOT_HOLD; + arg_table[Q3EGlobals.UI_1 * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_1 * 4 + 3] = 0; arg_table[Q3EGlobals.UI_2 * 4] = Q3EKeyCodes.KeyCodesD3BFG.K_2; - arg_table[Q3EGlobals.UI_2 * 4 + 1] = 0; - arg_table[Q3EGlobals.UI_2 * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_2 * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_NOT_HOLD; + arg_table[Q3EGlobals.UI_2 * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_2 * 4 + 3] = 0; arg_table[Q3EGlobals.UI_3 * 4] = Q3EKeyCodes.KeyCodesD3BFG.K_3; - arg_table[Q3EGlobals.UI_3 * 4 + 1] = 0; - arg_table[Q3EGlobals.UI_3 * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_3 * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_NOT_HOLD; + arg_table[Q3EGlobals.UI_3 * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_3 * 4 + 3] = 0; arg_table[Q3EGlobals.UI_KBD * 4] = Q3EKeyCodes.K_VKBD; - arg_table[Q3EGlobals.UI_KBD * 4 + 1] = 0; - arg_table[Q3EGlobals.UI_KBD * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_KBD * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_NOT_HOLD; + arg_table[Q3EGlobals.UI_KBD * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_KBD * 4 + 3] = 0; arg_table[Q3EGlobals.UI_CONSOLE * 4] = Q3EKeyCodes.KeyCodesD3.K_CONSOLE; - arg_table[Q3EGlobals.UI_CONSOLE * 4 + 1] = 0; - arg_table[Q3EGlobals.UI_CONSOLE * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_CONSOLE * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_NOT_HOLD; + arg_table[Q3EGlobals.UI_CONSOLE * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_CONSOLE * 4 + 3] = 0; arg_table[Q3EGlobals.UI_RUN * 4] = Q3EKeyCodes.KeyCodesD3.K_SHIFT; - arg_table[Q3EGlobals.UI_RUN * 4 + 1] = 1; - arg_table[Q3EGlobals.UI_RUN * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_RUN * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_CAN_HOLD; + arg_table[Q3EGlobals.UI_RUN * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_RUN * 4 + 3] = 0; arg_table[Q3EGlobals.UI_ZOOM * 4] = Q3EKeyCodes.KeyCodesD3.K_Z; - arg_table[Q3EGlobals.UI_ZOOM * 4 + 1] = 1; - arg_table[Q3EGlobals.UI_ZOOM * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_ZOOM * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_CAN_HOLD; + arg_table[Q3EGlobals.UI_ZOOM * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_ZOOM * 4 + 3] = 0; arg_table[Q3EGlobals.UI_INTERACT * 4] = Q3EKeyCodes.KeyCodesD3.K_MOUSE2; - arg_table[Q3EGlobals.UI_INTERACT * 4 + 1] = 0; - arg_table[Q3EGlobals.UI_INTERACT * 4 + 2] = 0; + arg_table[Q3EGlobals.UI_INTERACT * 4 + 1] = Q3EGlobals.ONSCRREN_BUTTON_NOT_HOLD; + arg_table[Q3EGlobals.UI_INTERACT * 4 + 2] = Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL; arg_table[Q3EGlobals.UI_INTERACT * 4 + 3] = 0; _defaultArgs = Arrays.copyOf(arg_table, arg_table.length); diff --git a/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/ButtonKey.java b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/ButtonKey.java new file mode 100644 index 000000000..22eb67619 --- /dev/null +++ b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/ButtonKey.java @@ -0,0 +1,18 @@ +package com.n0n3m4.q3e.onscreen; + +import com.n0n3m4.q3e.Q3EGlobals; + +public class ButtonKey implements OnScreenKey +{ + public int key; + public boolean hold; + public int style; + + @Override + public int[] ToArray() + { + return new int[] { + key, hold ? Q3EGlobals.ONSCRREN_BUTTON_CAN_HOLD : Q3EGlobals.ONSCRREN_BUTTON_NOT_HOLD, style, 0 + }; + } +} diff --git a/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/OnScreenKey.java b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/OnScreenKey.java new file mode 100644 index 000000000..69ec0b08f --- /dev/null +++ b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/OnScreenKey.java @@ -0,0 +1,6 @@ +package com.n0n3m4.q3e.onscreen; + +public interface OnScreenKey +{ + public int[] ToArray(); +} diff --git a/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/Q3EControls.java b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/Q3EControls.java index 23872f922..4d6abea27 100644 --- a/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/Q3EControls.java +++ b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/Q3EControls.java @@ -89,29 +89,6 @@ private static String ButtonLayoutStr(Number x, Number y, Number r_or_w, Number return x.intValue() + " " + y.intValue() + " " + r_or_w.intValue() + " " + a.intValue(); } - private static class ButtonLayout - { - public final int x, y, width_or_radius, alpha; - public ButtonLayout(Number x, Number y, Number r_or_w, Number a) - { - this.x = x.intValue(); - this.y = y.intValue(); - this.width_or_radius = r_or_w.intValue(); - this.alpha = a.intValue(); - } - - @Override - public String toString() - { - return x + " " + y + " " + width_or_radius + " " + alpha; - } - } - - private static int Dip2px(Activity context, int i, float scale) - { - return Math.round((float)Q3EUtils.dip2px(context, i) * scale); - } - public static String[] GetDefaultLayout(Activity context, boolean friendly, float scale, int opacity, boolean landscape) { return Q3EButtonLayoutManager.GetDefaultLayout(context, friendly, scale, opacity, landscape); diff --git a/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/Slider.java b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/Slider.java index bdeb85ac3..3f326eb66 100644 --- a/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/Slider.java +++ b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/Slider.java @@ -1,8 +1,10 @@ package com.n0n3m4.q3e.onscreen; +import android.util.Log; import android.view.View; import com.n0n3m4.q3e.Q3EControlView; +import com.n0n3m4.q3e.Q3EGlobals; import com.n0n3m4.q3e.Q3EUtils; import com.n0n3m4.q3e.gl.Q3EGL; @@ -31,6 +33,7 @@ public class Slider extends Paintable implements TouchListener int startx, starty; int SLIDE_DIST; public int style; + private int m_lastKey; public Slider(View vw, GL10 gl, int center_x, int center_y, int w, int h, String texid, int leftkey, int centerkey, int rightkey, int stl, float a) @@ -88,50 +91,70 @@ public boolean onTouchEvent(int x, int y, int act) { startx = x; starty = y; - } - if (act == -1) - { - if (style == 0) + if(style == Q3EGlobals.ONSCRREN_SLIDER_STYLE_LEFT_RIGHT_SPLIT_CLICK || style == Q3EGlobals.ONSCRREN_SLIDER_STYLE_DOWN_RIGHT_SPLIT_CLICK) { - if (x - startx < -SLIDE_DIST) + int key = KeyInPosition(x, y); + if(key > 0) { - controlView.sendKeyEvent(true, lkey, 0); - controlView.sendKeyEvent(false, lkey, 0); - } - else if (x - startx > SLIDE_DIST) - { - controlView.sendKeyEvent(true, rkey, 0); - controlView.sendKeyEvent(false, rkey, 0); - } - else - { - controlView.sendKeyEvent(true, ckey, 0); - controlView.sendKeyEvent(false, ckey, 0); + controlView.sendKeyEvent(true, key, 0); + m_lastKey = key; } } - - //k - else if (style == 1) + } + else if (act == -1) + { + switch (style) { - if ((y - starty > SLIDE_DIST) || (x - startx > SLIDE_DIST)) - { - double ang = Math.abs(Math.atan2(y - starty, x - startx)); - if (ang > Math.PI / 4 && ang < Math.PI * 3 / 4) + case Q3EGlobals.ONSCRREN_SLIDER_STYLE_LEFT_RIGHT: { + if (x - startx < -SLIDE_DIST) { controlView.sendKeyEvent(true, lkey, 0); controlView.sendKeyEvent(false, lkey, 0); } - else - { //k + else if (x - startx > SLIDE_DIST) + { controlView.sendKeyEvent(true, rkey, 0); controlView.sendKeyEvent(false, rkey, 0); - } //k + } + else + { + controlView.sendKeyEvent(true, ckey, 0); + controlView.sendKeyEvent(false, ckey, 0); + } } - else - { - controlView.sendKeyEvent(true, ckey, 0); - controlView.sendKeyEvent(false, ckey, 0); + break; + case Q3EGlobals.ONSCRREN_SLIDER_STYLE_DOWN_RIGHT: { + if ((y - starty > SLIDE_DIST) || (x - startx > SLIDE_DIST)) + { + double ang = Math.abs(Math.atan2(y - starty, x - startx)); + if (ang > Math.PI / 4 && ang < Math.PI * 3 / 4) + { + controlView.sendKeyEvent(true, lkey, 0); + controlView.sendKeyEvent(false, lkey, 0); + } + else + { //k + controlView.sendKeyEvent(true, rkey, 0); + controlView.sendKeyEvent(false, rkey, 0); + } //k + } + else + { + controlView.sendKeyEvent(true, ckey, 0); + controlView.sendKeyEvent(false, ckey, 0); + } + } + break; + case Q3EGlobals.ONSCRREN_SLIDER_STYLE_LEFT_RIGHT_SPLIT_CLICK: + case Q3EGlobals.ONSCRREN_SLIDER_STYLE_DOWN_RIGHT_SPLIT_CLICK: + default: { + if(m_lastKey > 0) + { + controlView.sendKeyEvent(false, m_lastKey, 0); + m_lastKey = 0; + } } + break; } } return true; @@ -140,12 +163,40 @@ else if (style == 1) @Override public boolean isInside(int x, int y) { - if (style == 0) + if (style == Q3EGlobals.ONSCRREN_SLIDER_STYLE_LEFT_RIGHT || style == Q3EGlobals.ONSCRREN_SLIDER_STYLE_LEFT_RIGHT_SPLIT_CLICK) return ((2 * Math.abs(cx - x) < width) && (2 * Math.abs(cy - y) < height)); else return ((2 * Math.abs(cx - x) < width) && (2 * Math.abs(cy - y) < height)) && (!((y > cy) && (x > cx))); } + private int KeyInPosition(int x, int y) + { + if (style == Q3EGlobals.ONSCRREN_SLIDER_STYLE_LEFT_RIGHT || style == Q3EGlobals.ONSCRREN_SLIDER_STYLE_LEFT_RIGHT_SPLIT_CLICK) + { + int deltaX = x - cx; + int slide_dist_2 = SLIDE_DIST / 2; + if (deltaX < -slide_dist_2) + return lkey; + else if (deltaX > slide_dist_2) + return rkey; + else + return ckey; + } + else + { + int deltaX = x - cx; + int deltaY = y - cy; + if (deltaX > 0 && deltaY < 0) + return rkey; + else if (deltaX < 0 && deltaY > 0) + return lkey; + else if (deltaX <= 0 && deltaY <= 0) + return ckey; + else + return 0; + } + } + public static Slider Move(Slider tmp, GL10 gl) { Slider news = new Slider(tmp.view, gl, tmp.cx, tmp.cy, tmp.width, tmp.height, tmp.tex_androidid, tmp.lkey, tmp.ckey, tmp.rkey, tmp.style, tmp.alpha); diff --git a/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/SliderKey.java b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/SliderKey.java new file mode 100644 index 000000000..c71311989 --- /dev/null +++ b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/SliderKey.java @@ -0,0 +1,18 @@ +package com.n0n3m4.q3e.onscreen; + +public class SliderKey implements OnScreenKey +{ + public int left_key; + public int middle_key; + public int right_key; + public int style; + + + @Override + public int[] ToArray() + { + return new int[] { + left_key, middle_key, right_key, style + }; + } +} diff --git a/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/UiLoader.java b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/UiLoader.java index 37e620da3..69f62a269 100644 --- a/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/UiLoader.java +++ b/Q3E/src/main/java/com/n0n3m4/q3e/onscreen/UiLoader.java @@ -43,29 +43,26 @@ public Object LoadElement(int id, boolean editMode) private Object LoadUiElement(int id, int cx, int cy, int size, int alpha, boolean editMode) { - int bh = size; - if (Q3EUtils.q3ei.arg_table[id * 4 + 2] == 0) - bh = size; - if (Q3EUtils.q3ei.arg_table[id * 4 + 2] == 1) - bh = size; - if (Q3EUtils.q3ei.arg_table[id * 4 + 2] == 2) - bh = size / 2; - - int sh = size; - if (Q3EUtils.q3ei.arg_table[id * 4 + 3] == 0) - sh = size / 2; - if (Q3EUtils.q3ei.arg_table[id * 4 + 3] == 1) - sh = size; - - switch (Q3EUtils.q3ei.type_table[id]) { case Q3EGlobals.TYPE_BUTTON: + int bh = size; + if (Q3EUtils.q3ei.arg_table[id * 4 + 2] == Q3EGlobals.ONSCREEN_BUTTON_TYPE_FULL) + bh = size; + else if (Q3EUtils.q3ei.arg_table[id * 4 + 2] == Q3EGlobals.ONSCREEN_BUTTON_TYPE_RIGHT_BOTTOM) + bh = size; + else if (Q3EUtils.q3ei.arg_table[id * 4 + 2] == Q3EGlobals.ONSCREEN_BUTTON_TYPE_CENTER) + bh = size / 2; return new Button(ctx, gl, cx, cy, size, bh, Q3EUtils.q3ei.texture_table[id], Q3EUtils.q3ei.arg_table[id * 4], Q3EUtils.q3ei.arg_table[id * 4 + 2], Q3EUtils.q3ei.arg_table[id * 4 + 1] == 1, (float) alpha / 100); case Q3EGlobals.TYPE_JOYSTICK: { return new Joystick(ctx, gl, size, (float) alpha / 100, cx, cy, Q3EUtils.q3ei.joystick_release_range, Q3EUtils.q3ei.joystick_inner_dead_zone, Q3EUtils.q3ei.joystick_unfixed, editMode, Q3EUtils.q3ei.texture_table[id]); } case Q3EGlobals.TYPE_SLIDER: + int sh = size; + if (Q3EUtils.q3ei.arg_table[id * 4 + 3] == Q3EGlobals.ONSCRREN_SLIDER_STYLE_LEFT_RIGHT || Q3EUtils.q3ei.arg_table[id * 4 + 3] == Q3EGlobals.ONSCRREN_SLIDER_STYLE_LEFT_RIGHT_SPLIT_CLICK) + sh = size / 2; + /*else if (Q3EUtils.q3ei.arg_table[id * 4 + 3] == Q3EGlobals.ONSCRREN_SLIDER_STYLE_DOWN_RIGHT) + sh = size;*/ return new Slider(ctx, gl, cx, cy, size, sh, Q3EUtils.q3ei.texture_table[id], Q3EUtils.q3ei.arg_table[id * 4], Q3EUtils.q3ei.arg_table[id * 4 + 1], Q3EUtils.q3ei.arg_table[id * 4 + 2], Q3EUtils.q3ei.arg_table[id * 4 + 3], (float) alpha / 100); case Q3EGlobals.TYPE_DISC: { diff --git a/Q3E/src/main/jni/doom3/neo/externlibs/soil/image_dxt.c b/Q3E/src/main/jni/doom3/neo/externlibs/soil/image_dxt.c new file mode 100644 index 000000000..648ebd764 --- /dev/null +++ b/Q3E/src/main/jni/doom3/neo/externlibs/soil/image_dxt.c @@ -0,0 +1,689 @@ +/* + Jonathan Dummer + 2007-07-31-10.32 + + simple DXT compression / decompression code + + public domain +*/ + +#include "image_dxt.h" +#include +#include +#include +#include + +/* set this =1 if you want to use the covarince matrix method... + which is better than my method of using standard deviations + overall, except on the infintesimal chance that the power + method fails for finding the largest eigenvector */ +#define USE_COV_MAT 1 + +/********* Function Prototypes *********/ +/* + Takes a 4x4 block of pixels and compresses it into 8 bytes + in DXT1 format (color only, no alpha). Speed is valued + over prettyness, at least for now. +*/ +void compress_DDS_color_block( + int channels, + const unsigned char *const uncompressed, + unsigned char compressed[8] ); +/* + Takes a 4x4 block of pixels and compresses the alpha + component it into 8 bytes for use in DXT5 DDS files. + Speed is valued over prettyness, at least for now. +*/ +void compress_DDS_alpha_block( + const unsigned char *const uncompressed, + unsigned char compressed[8] ); + +/********* Actual Exposed Functions *********/ +int + save_image_as_DDS + ( + const char *filename, + int width, int height, int channels, + const unsigned char *const data + ) +{ + /* variables */ + FILE *fout; + unsigned char *DDS_data; + DDS_header header; + int DDS_size; + /* error check */ + if( (NULL == filename) || + (width < 1) || (height < 1) || + (channels < 1) || (channels > 4) || + (data == NULL ) ) + { + return 0; + } + /* Convert the image */ + if( (channels & 1) == 1 ) + { + /* no alpha, just use DXT1 */ + DDS_data = convert_image_to_DXT1( data, width, height, channels, &DDS_size ); + } else + { + /* has alpha, so use DXT5 */ + DDS_data = convert_image_to_DXT5( data, width, height, channels, &DDS_size ); + } + /* save it */ + memset( &header, 0, sizeof( DDS_header ) ); + header.dwMagic = ('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24); + header.dwSize = 124; + header.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_LINEARSIZE; + header.dwWidth = width; + header.dwHeight = height; + header.dwPitchOrLinearSize = DDS_size; + header.sPixelFormat.dwSize = 32; + header.sPixelFormat.dwFlags = DDPF_FOURCC; + if( (channels & 1) == 1 ) + { + header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('1' << 24); + } else + { + header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('5' << 24); + } + header.sCaps.dwCaps1 = DDSCAPS_TEXTURE; + /* write it out */ + fout = fopen( filename, "wb"); + fwrite( &header, sizeof( DDS_header ), 1, fout ); + fwrite( DDS_data, 1, DDS_size, fout ); + fclose( fout ); + /* done */ + free( DDS_data ); + return 1; +} + +unsigned char* convert_image_to_DXT1( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size ) +{ + unsigned char *compressed; + int i, j, x, y; + unsigned char ublock[16*3]; + unsigned char cblock[8]; + int index = 0, chan_step = 1; + int block_count = 0; + /* error check */ + *out_size = 0; + if( (width < 1) || (height < 1) || + (NULL == uncompressed) || + (channels < 1) || (channels > 4) ) + { + return NULL; + } + /* for channels == 1 or 2, I do not step forward for R,G,B values */ + if( channels < 3 ) + { + chan_step = 0; + } + /* get the RAM for the compressed image + (8 bytes per 4x4 pixel block) */ + *out_size = ((width+3) >> 2) * ((height+3) >> 2) * 8; + compressed = (unsigned char*)malloc( *out_size ); + /* go through each block */ + for( j = 0; j < height; j += 4 ) + { + for( i = 0; i < width; i += 4 ) + { + /* copy this block into a new one */ + int idx = 0; + int mx = 4, my = 4; + if( j+4 >= height ) + { + my = height - j; + } + if( i+4 >= width ) + { + mx = width - i; + } + for( y = 0; y < my; ++y ) + { + for( x = 0; x < mx; ++x ) + { + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step+chan_step]; + } + for( x = mx; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + } + } + for( y = my; y < 4; ++y ) + { + for( x = 0; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + } + } + /* compress the block */ + ++block_count; + compress_DDS_color_block( 3, ublock, cblock ); + /* copy the data from the block into the main block */ + for( x = 0; x < 8; ++x ) + { + compressed[index++] = cblock[x]; + } + } + } + return compressed; +} + +unsigned char* convert_image_to_DXT5( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size ) +{ + unsigned char *compressed; + int i, j, x, y; + unsigned char ublock[16*4]; + unsigned char cblock[8]; + int index = 0, chan_step = 1; + int block_count = 0, has_alpha; + /* error check */ + *out_size = 0; + if( (width < 1) || (height < 1) || + (NULL == uncompressed) || + (channels < 1) || ( channels > 4) ) + { + return NULL; + } + /* for channels == 1 or 2, I do not step forward for R,G,B vales */ + if( channels < 3 ) + { + chan_step = 0; + } + /* # channels = 1 or 3 have no alpha, 2 & 4 do have alpha */ + has_alpha = 1 - (channels & 1); + /* get the RAM for the compressed image + (16 bytes per 4x4 pixel block) */ + *out_size = ((width+3) >> 2) * ((height+3) >> 2) * 16; + compressed = (unsigned char*)malloc( *out_size ); + /* go through each block */ + for( j = 0; j < height; j += 4 ) + { + for( i = 0; i < width; i += 4 ) + { + /* local variables, and my block counter */ + int idx = 0; + int mx = 4, my = 4; + if( j+4 >= height ) + { + my = height - j; + } + if( i+4 >= width ) + { + mx = width - i; + } + for( y = 0; y < my; ++y ) + { + for( x = 0; x < mx; ++x ) + { + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step]; + ublock[idx++] = uncompressed[(j+y)*width*channels+(i+x)*channels+chan_step+chan_step]; + ublock[idx++] = + has_alpha * uncompressed[(j+y)*width*channels+(i+x)*channels+channels-1] + + (1-has_alpha)*255; + } + for( x = mx; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + ublock[idx++] = ublock[3]; + } + } + for( y = my; y < 4; ++y ) + { + for( x = 0; x < 4; ++x ) + { + ublock[idx++] = ublock[0]; + ublock[idx++] = ublock[1]; + ublock[idx++] = ublock[2]; + ublock[idx++] = ublock[3]; + } + } + /* now compress the alpha block */ + compress_DDS_alpha_block( ublock, cblock ); + /* copy the data from the compressed alpha block into the main buffer */ + for( x = 0; x < 8; ++x ) + { + compressed[index++] = cblock[x]; + } + /* then compress the color block */ + ++block_count; + compress_DDS_color_block( 4, ublock, cblock ); + /* copy the data from the compressed color block into the main buffer */ + for( x = 0; x < 8; ++x ) + { + compressed[index++] = cblock[x]; + } + } + } + return compressed; +} + +/********* Helper Functions *********/ +int convert_bit_range( int c, int from_bits, int to_bits ) +{ + int b = (1 << (from_bits - 1)) + c * ((1 << to_bits) - 1); + return (b + (b >> from_bits)) >> from_bits; +} + +int rgb_to_565( int r, int g, int b ) +{ + return + (convert_bit_range( r, 8, 5 ) << 11) | + (convert_bit_range( g, 8, 6 ) << 05) | + (convert_bit_range( b, 8, 5 ) << 00); +} + +void rgb_888_from_565( unsigned int c, int *r, int *g, int *b ) +{ + *r = convert_bit_range( (c >> 11) & 31, 5, 8 ); + *g = convert_bit_range( (c >> 05) & 63, 6, 8 ); + *b = convert_bit_range( (c >> 00) & 31, 5, 8 ); +} + +void compute_color_line_STDEV( + const unsigned char *const uncompressed, + int channels, + float point[3], float direction[3] ) +{ + const float inv_16 = 1.0f / 16.0f; + int i; + float sum_r = 0.0f, sum_g = 0.0f, sum_b = 0.0f; + float sum_rr = 0.0f, sum_gg = 0.0f, sum_bb = 0.0f; + float sum_rg = 0.0f, sum_rb = 0.0f, sum_gb = 0.0f; + /* calculate all data needed for the covariance matrix + ( to compare with _rygdxt code) */ + for( i = 0; i < 16*channels; i += channels ) + { + sum_r += uncompressed[i+0]; + sum_rr += uncompressed[i+0] * uncompressed[i+0]; + sum_g += uncompressed[i+1]; + sum_gg += uncompressed[i+1] * uncompressed[i+1]; + sum_b += uncompressed[i+2]; + sum_bb += uncompressed[i+2] * uncompressed[i+2]; + sum_rg += uncompressed[i+0] * uncompressed[i+1]; + sum_rb += uncompressed[i+0] * uncompressed[i+2]; + sum_gb += uncompressed[i+1] * uncompressed[i+2]; + } + /* convert the sums to averages */ + sum_r *= inv_16; + sum_g *= inv_16; + sum_b *= inv_16; + /* and convert the squares to the squares of the value - avg_value */ + sum_rr -= 16.0f * sum_r * sum_r; + sum_gg -= 16.0f * sum_g * sum_g; + sum_bb -= 16.0f * sum_b * sum_b; + sum_rg -= 16.0f * sum_r * sum_g; + sum_rb -= 16.0f * sum_r * sum_b; + sum_gb -= 16.0f * sum_g * sum_b; + /* the point on the color line is the average */ + point[0] = sum_r; + point[1] = sum_g; + point[2] = sum_b; + #if USE_COV_MAT + /* + The following idea was from ryg. + (https://mollyrocket.com/forums/viewtopic.php?t=392) + The method worked great (less RMSE than mine) most of + the time, but had some issues handling some simple + boundary cases, like full green next to full red, + which would generate a covariance matrix like this: + + | 1 -1 0 | + | -1 1 0 | + | 0 0 0 | + + For a given starting vector, the power method can + generate all zeros! So no starting with {1,1,1} + as I was doing! This kind of error is still a + slight posibillity, but will be very rare. + */ + /* use the covariance matrix directly + (1st iteration, don't use all 1.0 values!) */ + sum_r = 1.0f; + sum_g = 2.718281828f; + sum_b = 3.141592654f; + direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; + direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; + direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; + /* 2nd iteration, use results from the 1st guy */ + sum_r = direction[0]; + sum_g = direction[1]; + sum_b = direction[2]; + direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; + direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; + direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; + /* 3rd iteration, use results from the 2nd guy */ + sum_r = direction[0]; + sum_g = direction[1]; + sum_b = direction[2]; + direction[0] = sum_r*sum_rr + sum_g*sum_rg + sum_b*sum_rb; + direction[1] = sum_r*sum_rg + sum_g*sum_gg + sum_b*sum_gb; + direction[2] = sum_r*sum_rb + sum_g*sum_gb + sum_b*sum_bb; + #else + /* use my standard deviation method + (very robust, a tiny bit slower and less accurate) */ + direction[0] = sqrt( sum_rr ); + direction[1] = sqrt( sum_gg ); + direction[2] = sqrt( sum_bb ); + /* which has a greater component */ + if( sum_gg > sum_rr ) + { + /* green has greater component, so base the other signs off of green */ + if( sum_rg < 0.0f ) + { + direction[0] = -direction[0]; + } + if( sum_gb < 0.0f ) + { + direction[2] = -direction[2]; + } + } else + { + /* red has a greater component */ + if( sum_rg < 0.0f ) + { + direction[1] = -direction[1]; + } + if( sum_rb < 0.0f ) + { + direction[2] = -direction[2]; + } + } + #endif +} + +void LSE_master_colors_max_min( + int *cmax, int *cmin, + int channels, + const unsigned char *const uncompressed ) +{ + int i, j; + /* the master colors */ + int c0[3], c1[3]; + /* used for fitting the line */ + float sum_x[] = { 0.0f, 0.0f, 0.0f }; + float sum_x2[] = { 0.0f, 0.0f, 0.0f }; + float dot_max = 1.0f, dot_min = -1.0f; + float vec_len2 = 0.0f; + float dot; + /* error check */ + if( (channels < 3) || (channels > 4) ) + { + return; + } + compute_color_line_STDEV( uncompressed, channels, sum_x, sum_x2 ); + vec_len2 = 1.0f / ( 0.00001f + + sum_x2[0]*sum_x2[0] + sum_x2[1]*sum_x2[1] + sum_x2[2]*sum_x2[2] ); + /* finding the max and min vector values */ + dot_max = + ( + sum_x2[0] * uncompressed[0] + + sum_x2[1] * uncompressed[1] + + sum_x2[2] * uncompressed[2] + ); + dot_min = dot_max; + for( i = 1; i < 16; ++i ) + { + dot = + ( + sum_x2[0] * uncompressed[i*channels+0] + + sum_x2[1] * uncompressed[i*channels+1] + + sum_x2[2] * uncompressed[i*channels+2] + ); + if( dot < dot_min ) + { + dot_min = dot; + } else if( dot > dot_max ) + { + dot_max = dot; + } + } + /* and the offset (from the average location) */ + dot = sum_x2[0]*sum_x[0] + sum_x2[1]*sum_x[1] + sum_x2[2]*sum_x[2]; + dot_min -= dot; + dot_max -= dot; + /* post multiply by the scaling factor */ + dot_min *= vec_len2; + dot_max *= vec_len2; + /* OK, build the master colors */ + for( i = 0; i < 3; ++i ) + { + /* color 0 */ + c0[i] = (int)(0.5f + sum_x[i] + dot_max * sum_x2[i]); + if( c0[i] < 0 ) + { + c0[i] = 0; + } else if( c0[i] > 255 ) + { + c0[i] = 255; + } + /* color 1 */ + c1[i] = (int)(0.5f + sum_x[i] + dot_min * sum_x2[i]); + if( c1[i] < 0 ) + { + c1[i] = 0; + } else if( c1[i] > 255 ) + { + c1[i] = 255; + } + } + /* down_sample (with rounding?) */ + i = rgb_to_565( c0[0], c0[1], c0[2] ); + j = rgb_to_565( c1[0], c1[1], c1[2] ); + if( i > j ) + { + *cmax = i; + *cmin = j; + } else + { + *cmax = j; + *cmin = i; + } +} + +void + compress_DDS_color_block + ( + int channels, + const unsigned char *const uncompressed, + unsigned char compressed[8] + ) +{ + /* variables */ + int i; + int next_bit; + int enc_c0, enc_c1; + int c0[4], c1[4]; + float color_line[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + float vec_len2 = 0.0f, dot_offset = 0.0f; + /* stupid order */ + int swizzle4[] = { 0, 2, 3, 1 }; + /* get the master colors */ + LSE_master_colors_max_min( &enc_c0, &enc_c1, channels, uncompressed ); + /* store the 565 color 0 and color 1 */ + compressed[0] = (enc_c0 >> 0) & 255; + compressed[1] = (enc_c0 >> 8) & 255; + compressed[2] = (enc_c1 >> 0) & 255; + compressed[3] = (enc_c1 >> 8) & 255; + /* zero out the compressed data */ + compressed[4] = 0; + compressed[5] = 0; + compressed[6] = 0; + compressed[7] = 0; + /* reconstitute the master color vectors */ + rgb_888_from_565( enc_c0, &c0[0], &c0[1], &c0[2] ); + rgb_888_from_565( enc_c1, &c1[0], &c1[1], &c1[2] ); + /* the new vector */ + vec_len2 = 0.0f; + for( i = 0; i < 3; ++i ) + { + color_line[i] = (float)(c1[i] - c0[i]); + vec_len2 += color_line[i] * color_line[i]; + } + if( vec_len2 > 0.0f ) + { + vec_len2 = 1.0f / vec_len2; + } + /* pre-proform the scaling */ + color_line[0] *= vec_len2; + color_line[1] *= vec_len2; + color_line[2] *= vec_len2; + /* compute the offset (constant) portion of the dot product */ + dot_offset = color_line[0]*c0[0] + color_line[1]*c0[1] + color_line[2]*c0[2]; + /* store the rest of the bits */ + next_bit = 8*4; + for( i = 0; i < 16; ++i ) + { + /* find the dot product of this color, to place it on the line + (should be [-1,1]) */ + int next_value = 0; + float dot_product = + color_line[0] * uncompressed[i*channels+0] + + color_line[1] * uncompressed[i*channels+1] + + color_line[2] * uncompressed[i*channels+2] - + dot_offset; + /* map to [0,3] */ + next_value = (int)( dot_product * 3.0f + 0.5f ); + if( next_value > 3 ) + { + next_value = 3; + } else if( next_value < 0 ) + { + next_value = 0; + } + /* OK, store this value */ + compressed[next_bit >> 3] |= swizzle4[ next_value ] << (next_bit & 7); + next_bit += 2; + } + /* done compressing to DXT1 */ +} + +void + compress_DDS_alpha_block + ( + const unsigned char *const uncompressed, + unsigned char compressed[8] + ) +{ + /* variables */ + int i; + int next_bit; + int a0, a1; + float scale_me; + /* stupid order */ + int swizzle8[] = { 1, 7, 6, 5, 4, 3, 2, 0 }; + /* get the alpha limits (a0 > a1) */ + a0 = a1 = uncompressed[3]; + for( i = 4+3; i < 16*4; i += 4 ) + { + if( uncompressed[i] > a0 ) + { + a0 = uncompressed[i]; + } else if( uncompressed[i] < a1 ) + { + a1 = uncompressed[i]; + } + } + /* store those limits, and zero the rest of the compressed dataset */ + compressed[0] = a0; + compressed[1] = a1; + /* zero out the compressed data */ + compressed[2] = 0; + compressed[3] = 0; + compressed[4] = 0; + compressed[5] = 0; + compressed[6] = 0; + compressed[7] = 0; + /* store the all of the alpha values */ + next_bit = 8*2; + scale_me = 7.9999f / (a0 - a1); + for( i = 3; i < 16*4; i += 4 ) + { + /* convert this alpha value to a 3 bit number */ + int svalue; + int value = (int)((uncompressed[i] - a1) * scale_me); + svalue = swizzle8[ value&7 ]; + /* OK, store this value, start with the 1st byte */ + compressed[next_bit >> 3] |= svalue << (next_bit & 7); + if( (next_bit & 7) > 5 ) + { + /* spans 2 bytes, fill in the start of the 2nd byte */ + compressed[1 + (next_bit >> 3)] |= svalue >> (8 - (next_bit & 7) ); + } + next_bit += 3; + } + /* done compressing to DXT1 */ +} + +typedef void (*soil_write_func)(void *context, void *data, int size); +int +soil_save_image_as_func + ( + soil_write_func func, void *context, + int width, int height, int channels, + const unsigned char *const data + ) +{ + /* variables */ + unsigned char *DDS_data; + DDS_header header; + int DDS_size; + /* error check */ + if( (NULL == func) || + (width < 1) || (height < 1) || + (channels < 1) || (channels > 4) || + (data == NULL ) ) + { + return 0; + } + /* Convert the image */ + if( (channels & 1) == 1 ) + { + /* no alpha, just use DXT1 */ + DDS_data = convert_image_to_DXT1( data, width, height, channels, &DDS_size ); + } else + { + /* has alpha, so use DXT5 */ + DDS_data = convert_image_to_DXT5( data, width, height, channels, &DDS_size ); + } + /* save it */ + memset( &header, 0, sizeof( DDS_header ) ); + header.dwMagic = ('D' << 0) | ('D' << 8) | ('S' << 16) | (' ' << 24); + header.dwSize = 124; + header.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_LINEARSIZE; + header.dwWidth = width; + header.dwHeight = height; + header.dwPitchOrLinearSize = DDS_size; + header.sPixelFormat.dwSize = 32; + header.sPixelFormat.dwFlags = DDPF_FOURCC; + if( (channels & 1) == 1 ) + { + header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('1' << 24); + } else + { + header.sPixelFormat.dwFourCC = ('D' << 0) | ('X' << 8) | ('T' << 16) | ('5' << 24); + } + header.sCaps.dwCaps1 = DDSCAPS_TEXTURE; + /* write it out */ + func( context, &header, sizeof( DDS_header ) ); + func( context, DDS_data, DDS_size ); + /* done */ + free( DDS_data ); + return 1; +} diff --git a/Q3E/src/main/jni/doom3/neo/externlibs/soil/image_dxt.h b/Q3E/src/main/jni/doom3/neo/externlibs/soil/image_dxt.h new file mode 100644 index 000000000..81aed0f95 --- /dev/null +++ b/Q3E/src/main/jni/doom3/neo/externlibs/soil/image_dxt.h @@ -0,0 +1,133 @@ +/* + Jonathan Dummer + 2007-07-31-10.32 + + simple DXT compression / decompression code + + public domain +*/ + +#ifndef HEADER_IMAGE_DXT +#define HEADER_IMAGE_DXT + +/** + Converts an image from an array of unsigned chars (RGB or RGBA) to + DXT1 or DXT5, then saves the converted image to disk. + \return 0 if failed, otherwise returns 1 +**/ +int +save_image_as_DDS +( + const char *filename, + int width, int height, int channels, + const unsigned char *const data +); + +/** + take an image and convert it to DXT1 (no alpha) +**/ +unsigned char* +convert_image_to_DXT1 +( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size +); + +/** + take an image and convert it to DXT5 (with alpha) +**/ +unsigned char* +convert_image_to_DXT5 +( + const unsigned char *const uncompressed, + int width, int height, int channels, + int *out_size +); + +/** A bunch of DirectDraw Surface structures and flags **/ +typedef struct +{ + unsigned int dwMagic; + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwHeight; + unsigned int dwWidth; + unsigned int dwPitchOrLinearSize; + unsigned int dwDepth; + unsigned int dwMipMapCount; + unsigned int dwReserved1[ 11 ]; + + /* DDPIXELFORMAT */ + struct + { + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwFourCC; + unsigned int dwRGBBitCount; + unsigned int dwRBitMask; + unsigned int dwGBitMask; + unsigned int dwBBitMask; + unsigned int dwAlphaBitMask; + } + sPixelFormat; + + /* DDCAPS2 */ + struct + { + unsigned int dwCaps1; + unsigned int dwCaps2; + unsigned int dwDDSX; + unsigned int dwReserved; + } + sCaps; + unsigned int dwReserved2; +} +DDS_header ; + +/* the following constants were copied directly off the MSDN website */ + +/* The dwFlags member of the original DDSURFACEDESC2 structure + can be set to one or more of the following values. */ +#define DDSD_CAPS 0x00000001 +#define DDSD_HEIGHT 0x00000002 +#define DDSD_WIDTH 0x00000004 +#define DDSD_PITCH 0x00000008 +#define DDSD_PIXELFORMAT 0x00001000 +#define DDSD_MIPMAPCOUNT 0x00020000 +#define DDSD_LINEARSIZE 0x00080000 +#define DDSD_DEPTH 0x00800000 + +/* DirectDraw Pixel Format */ +#define DDPF_ALPHAPIXELS 0x00000001 +#define DDPF_FOURCC 0x00000004 +#define DDPF_RGB 0x00000040 + +/* The dwCaps1 member of the DDSCAPS2 structure can be + set to one or more of the following values. */ +#define DDSCAPS_COMPLEX 0x00000008 +#define DDSCAPS_TEXTURE 0x00001000 +#define DDSCAPS_MIPMAP 0x00400000 + +/* The dwCaps2 member of the DDSCAPS2 structure can be + set to one or more of the following values. */ +#define DDSCAPS2_CUBEMAP 0x00000200 +#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400 +#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800 +#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000 +#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000 +#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000 +#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000 +#define DDSCAPS2_VOLUME 0x00200000 + +typedef void (*soil_write_func)(void *context, void *data, int size); +int +soil_save_image_as_func + ( + soil_write_func func, void *context, + int width, int height, int channels, + const unsigned char *const data + ); + +#include "image_dxt.c" +#endif /* HEADER_IMAGE_DXT */ diff --git a/Q3E/src/main/jni/doom3/neo/externlibs/soil/soil_dds_image.h b/Q3E/src/main/jni/doom3/neo/externlibs/soil/soil_dds_image.h index abd1559bc..5afc54f9b 100644 --- a/Q3E/src/main/jni/doom3/neo/externlibs/soil/soil_dds_image.h +++ b/Q3E/src/main/jni/doom3/neo/externlibs/soil/soil_dds_image.h @@ -155,6 +155,7 @@ static unsigned char *convert_format(unsigned char *data, int img_n, int req_com } #include "stbi_dds_aug.h" +#include "image_dxt.h" #include "stbi_dds_aug_c.h" #ifdef __cplusplus diff --git a/Q3E/src/main/jni/doom3/neo/externlibs/soil/stbi_dds_aug_c.h b/Q3E/src/main/jni/doom3/neo/externlibs/soil/stbi_dds_aug_c.h index f49407a7f..0b0957cc5 100644 --- a/Q3E/src/main/jni/doom3/neo/externlibs/soil/stbi_dds_aug_c.h +++ b/Q3E/src/main/jni/doom3/neo/externlibs/soil/stbi_dds_aug_c.h @@ -2,40 +2,6 @@ /// DDS file support, does decoding, _not_ direct uploading /// (use SOIL for that ;-) -/// A bunch of DirectDraw Surface structures and flags -typedef struct { - unsigned int dwMagic; - unsigned int dwSize; - unsigned int dwFlags; - unsigned int dwHeight; - unsigned int dwWidth; - unsigned int dwPitchOrLinearSize; - unsigned int dwDepth; - unsigned int dwMipMapCount; - unsigned int dwReserved1[ 11 ]; - - // DDPIXELFORMAT - struct { - unsigned int dwSize; - unsigned int dwFlags; - unsigned int dwFourCC; - unsigned int dwRGBBitCount; - unsigned int dwRBitMask; - unsigned int dwGBitMask; - unsigned int dwBBitMask; - unsigned int dwAlphaBitMask; - } sPixelFormat; - - // DDCAPS2 - struct { - unsigned int dwCaps1; - unsigned int dwCaps2; - unsigned int dwDDSX; - unsigned int dwReserved; - } sCaps; - unsigned int dwReserved2; -} DDS_header ; - // the following constants were copied directly off the MSDN website // The dwFlags member of the original DDSURFACEDESC2 structure @@ -508,4 +474,4 @@ stbi_uc *stbi_dds_load_from_memory (stbi_uc const *buffer, int len, int *x, int stbi s; start_mem(&s,buffer, len); return dds_load(&s,x,y,comp,req_comp); -} +} \ No newline at end of file diff --git a/Q3E/src/main/jni/doom3/neo/framework/CmdSystem.h b/Q3E/src/main/jni/doom3/neo/framework/CmdSystem.h index 75b59ae64..f8921665a 100644 --- a/Q3E/src/main/jni/doom3/neo/framework/CmdSystem.h +++ b/Q3E/src/main/jni/doom3/neo/framework/CmdSystem.h @@ -173,7 +173,14 @@ ID_INLINE void idCmdSystem::ArgCompletion_MapName(const idCmdArgs &args, void(*c ID_INLINE void idCmdSystem::ArgCompletion_ModelName(const idCmdArgs &args, void(*callback)(const char *s)) { - cmdSystem->ArgCompletion_FolderExtension(args, callback, "models/", false, ".lwo", ".ase", ".md5mesh", ".ma", NULL); + cmdSystem->ArgCompletion_FolderExtension(args, callback, "models/", false, ".lwo", ".ase", ".md5mesh", ".ma" +#ifdef _MODEL_OBJ + , ".obj" +#endif +#ifdef _MODEL_DAE + , ".dae" +#endif + , NULL); } ID_INLINE void idCmdSystem::ArgCompletion_SoundName(const idCmdArgs &args, void(*callback)(const char *s)) @@ -183,7 +190,11 @@ ID_INLINE void idCmdSystem::ArgCompletion_SoundName(const idCmdArgs &args, void( ID_INLINE void idCmdSystem::ArgCompletion_ImageName(const idCmdArgs &args, void(*callback)(const char *s)) { - cmdSystem->ArgCompletion_FolderExtension(args, callback, "/", false, ".tga", ".dds", ".jpg", ".pcx", NULL); + cmdSystem->ArgCompletion_FolderExtension(args, callback, "/", false, ".tga", ".dds", ".jpg", ".pcx" +#ifdef _USING_STB + , ".jpeg", ".png", "dds", "bmp" +#endif + , NULL); } ID_INLINE void idCmdSystem::ArgCompletion_VideoName(const idCmdArgs &args, void(*callback)(const char *s)) diff --git a/Q3E/src/main/jni/doom3/neo/renderer/Image.h b/Q3E/src/main/jni/doom3/neo/renderer/Image.h index e12dd64c3..bad4ac72b 100644 --- a/Q3E/src/main/jni/doom3/neo/renderer/Image.h +++ b/Q3E/src/main/jni/doom3/neo/renderer/Image.h @@ -606,8 +606,14 @@ void R_LoadImageProgram(const char *name, byte **pic, int *width, int *height, I const char *R_ParsePastImageProgram(idLexer &src); #ifdef _USING_STB -void R_WritePNG(const char *filename, const byte *data, int width, int height, int comp, int quality = 100, bool flipVertical = false, const char *basePath = NULL); -void R_WriteJPG(const char *filename, const byte *data, int width, int height, int comp, int compression = 0, bool flipVertical = false, const char *basePath = NULL); +void LoadJPG_stb(const char *filename, unsigned char **pic, int *width, int *height, ID_TIME_T *timestamp); +void LoadPNG(const char *filename, byte **pic, int *width, int *height, ID_TIME_T *timestamp); +void LoadDDS(const char *filename, byte **pic, int *width, int *height, ID_TIME_T *timestamp); + +void R_WritePNG(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical = false, int quality = 100, const char *basePath = NULL); +void R_WriteJPG(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical = false, int compression = 0, const char *basePath = NULL); void R_WriteBMP(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical = false, const char *basePath = NULL); -void R_WriteImage(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical = false, const char *basePath = NULL); +void R_WriteDDS(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical, const char *basePath = NULL); + +void R_WriteScreenshotImage(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical = false, const char *basePath = NULL); #endif diff --git a/Q3E/src/main/jni/doom3/neo/renderer/Image_files.cpp b/Q3E/src/main/jni/doom3/neo/renderer/Image_files.cpp index 2a6487c6f..4128e64f6 100644 --- a/Q3E/src/main/jni/doom3/neo/renderer/Image_files.cpp +++ b/Q3E/src/main/jni/doom3/neo/renderer/Image_files.cpp @@ -47,19 +47,7 @@ void R_LoadImage( const char *name, byte **pic, int *width, int *height, bool ma * You may also wish to include "jerror.h". */ -#ifdef _USING_STB -#define STB_IMAGE_IMPLEMENTATION -#define STBI_NO_HDR -#define STBI_NO_LINEAR -#define STBI_ONLY_JPEG // at least for now, only use it for JPEG -#define STBI_NO_STDIO // images are passed as buffers - -#define STBI_ONLY_PNG - -#include "../externlibs/stb/stb_image.h" - -#else - +#if !defined(_USING_STB) extern "C" { #include @@ -183,124 +171,6 @@ void R_WritePalTGA(const char *filename, const byte *data, const byte *palette, static void LoadBMP(const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp); static void LoadTGA(const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp); static void LoadJPG(const char *name, byte **pic, int *width, int *height, ID_TIME_T *timestamp); -#ifdef _USING_STB -static void LoadPNG(const char *filename, byte **pic, int *width, int *height, ID_TIME_T *timestamp) -{ - - byte *fbuffer; - int len; - - if (pic) { - *pic = NULL; // until proven otherwise - } - - { - idFile *f; - - f = fileSystem->OpenFileRead(filename); - - if (!f) { - return; - } - - len = f->Length(); - - if (timestamp) { - *timestamp = f->Timestamp(); - } - - if (!pic) { - fileSystem->CloseFile(f); - return; // just getting timestamp - } - - fbuffer = (byte *)Mem_ClearedAlloc(len); - f->Read(fbuffer, len); - fileSystem->CloseFile(f); - } - - int w=0, h=0, comp=0; - byte* decodedImageData = stbi_load_from_memory( fbuffer, len, &w, &h, &comp, STBI_rgb_alpha ); - - Mem_Free( fbuffer ); - - if ( decodedImageData == NULL ) { - common->Warning( "stb_image was unable to load PNG %s : %s\n", - filename, stbi_failure_reason()); - return; - } - - // *pic must be allocated with R_StaticAlloc(), but stb_image allocates with malloc() - // (and as there is no R_StaticRealloc(), #define STBI_MALLOC etc won't help) - // so the decoded data must be copied once - int size = w*h*4; - *pic = (byte *)R_StaticAlloc( size ); - memcpy( *pic, decodedImageData, size ); - *width = w; - *height = h; - // now that decodedImageData has been copied into *pic, it's not needed anymore - stbi_image_free( decodedImageData ); -} - -#include "../externlibs/soil/soil_dds_image.h" -static void LoadDDS(const char *filename, byte **pic, int *width, int *height, ID_TIME_T *timestamp) -{ - - byte *fbuffer; - int len; - - if (pic) { - *pic = NULL; // until proven otherwise - } - - { - idFile *f; - - f = fileSystem->OpenFileRead(filename); - - if (!f) { - return; - } - - len = f->Length(); - - if (timestamp) { - *timestamp = f->Timestamp(); - } - - if (!pic) { - fileSystem->CloseFile(f); - return; // just getting timestamp - } - - fbuffer = (byte *)Mem_ClearedAlloc(len); - f->Read(fbuffer, len); - fileSystem->CloseFile(f); - } - - int w=0, h=0, comp=0; - byte* decodedImageData = stbi_dds_load_from_memory( fbuffer, len, &w, &h, &comp, STBI_rgb_alpha ); - - Mem_Free( fbuffer ); - - if ( decodedImageData == NULL ) { - common->Warning( "stb_image was unable to load PNG %s : %s\n", - filename, stbi_failure_reason()); - return; - } - - // *pic must be allocated with R_StaticAlloc(), but stb_image allocates with malloc() - // (and as there is no R_StaticRealloc(), #define STBI_MALLOC etc won't help) - // so the decoded data must be copied once - int size = w*h*4; - *pic = (byte *)R_StaticAlloc( size ); - memcpy( *pic, decodedImageData, size ); - *width = w; - *height = h; - // now that decodedImageData has been copied into *pic, it's not needed anymore - stbi_image_free( decodedImageData ); -} -#endif /* ======================================================================== @@ -948,59 +818,7 @@ LoadJPG static void LoadJPG(const char *filename, unsigned char **pic, int *width, int *height, ID_TIME_T *timestamp) { #ifdef _USING_STB - byte *fbuffer; - int len; - - if (pic) { - *pic = NULL; // until proven otherwise - } - - { - idFile *f; - - f = fileSystem->OpenFileRead(filename); - - if (!f) { - return; - } - - len = f->Length(); - - if (timestamp) { - *timestamp = f->Timestamp(); - } - - if (!pic) { - fileSystem->CloseFile(f); - return; // just getting timestamp - } - - fbuffer = (byte *)Mem_ClearedAlloc(len); - f->Read(fbuffer, len); - fileSystem->CloseFile(f); - } - - int w=0, h=0, comp=0; - byte* decodedImageData = stbi_load_from_memory( fbuffer, len, &w, &h, &comp, STBI_rgb_alpha ); - - Mem_Free( fbuffer ); - - if ( decodedImageData == NULL ) { - common->Warning( "stb_image was unable to load JPG %s : %s\n", - filename, stbi_failure_reason()); - return; - } - - // *pic must be allocated with R_StaticAlloc(), but stb_image allocates with malloc() - // (and as there is no R_StaticRealloc(), #define STBI_MALLOC etc won't help) - // so the decoded data must be copied once - int size = w*h*4; - *pic = (byte *)R_StaticAlloc( size ); - memcpy( *pic, decodedImageData, size ); - *width = w; - *height = h; - // now that decodedImageData has been copied into *pic, it's not needed anymore - stbi_image_free( decodedImageData ); + LoadJPG_stb(filename, pic, width, height, timestamp); #else /* This struct contains the JPEG decompression parameters and pointers to * working space (which is allocated as needed by the JPEG library). @@ -1433,145 +1251,5 @@ bool R_LoadCubeImages(const char *imgName, cubeFiles_t extensions, byte *pics[6] } #ifdef _USING_STB -#define STB_IMAGE_WRITE_IMPLEMENTATION -#include "../externlibs/stb/stb_image_write.h" - -static void R_ImageFlipVertical(byte *data, int width, int height, int comp) -{ - int i; - byte *line1, *line2; - byte *swapBuffer; - int lineSize = width * comp; - - swapBuffer = (byte *)malloc(width * comp); - for (i = 0; i < height / 2; ++i) { - line1 = &data[i * lineSize]; - line2 = &data[(height - i - 1) * lineSize]; - memcpy(swapBuffer, line1, lineSize); - memcpy(line1, line2, lineSize); - memcpy(line2, swapBuffer, lineSize); - } - free(swapBuffer); -} - -#ifdef _HARM_IMAGE_RGBA_TO_RGB -static void R_ImageRGBA8888ToRGB888(const byte *data, int width, int height, byte *to) -{ - int i, j; - const byte *temp; - byte *target; - - for (i = 0 ; i < width ; i++) { - for (j = 0 ; j < height ; j++) { - temp = data + j * 4 * width + i * 4; - target = to + j * 3 * width + i * 3; - memcpy(target, temp, 3); - } - } -} -#endif - -static void R_StbWriteImageFile(void *context, void *data, int size) -{ - idFile *f = (idFile *)context; - f->Write(data, size); -} - -void R_WritePNG(const char *filename, const byte *data, int width, int height, int comp, int quality, bool flipVertical, const char *basePath) -{ - stbi_write_png_compression_level = idMath::ClampInt(0, 9, quality); - byte *ndata = NULL; - if(flipVertical) - { - ndata = (byte *) malloc(width * height * comp); - memcpy(ndata, data, width * height * comp); - R_ImageFlipVertical(ndata, width, height, comp); - data = ndata; - } - if(!basePath) - basePath = "fs_savepath"; - idFile *f = fileSystem->OpenFileWrite(filename, basePath); - if(f) - { - int r = stbi_write_png_to_func(R_StbWriteImageFile, (void *)f, width, height, comp, data, width * comp); - free(ndata); - if(!r) - common->Warning("R_WritePNG fail: %d", r); - fileSystem->CloseFile(f); - } -} - -void R_WriteJPG(const char *filename, const byte *data, int width, int height, int comp, int compression, bool flipVertical, const char *basePath) -{ - byte *ndata = NULL; - if(flipVertical) - { - ndata = (byte *) malloc(width * height * comp); - memcpy(ndata, data, width * height * comp); - R_ImageFlipVertical(ndata, width, height, comp); - data = ndata; - } - if(!basePath) - basePath = "fs_savepath"; - idFile *f = fileSystem->OpenFileWrite(filename, basePath); - if(f) - { - int r = stbi_write_jpg_to_func(R_StbWriteImageFile, (void *)f, width, height, comp, data, idMath::ClampInt(1, 100, compression)); - free(ndata); - if(!r) - common->Warning("R_WriteJPG fail: %d", r); - fileSystem->CloseFile(f); - } -} - -void R_WriteBMP(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical, const char *basePath) -{ - byte *ndata = NULL; - if(flipVertical) - { - ndata = (byte *) malloc(width * height * comp); - memcpy(ndata, data, width * height * comp); - R_ImageFlipVertical(ndata, width, height, comp); - data = ndata; - } - if(!basePath) - basePath = "fs_savepath"; - idFile *f = fileSystem->OpenFileWrite(filename, basePath); - if(f) - { - int r = stbi_write_bmp_to_func(R_StbWriteImageFile, (void *)f, width, height, comp, data); - free(ndata); - if(!r) - common->Warning("R_WriteBMP fail: %d", r); - fileSystem->CloseFile(f); - } -} - -void R_WriteImage(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical, const char *basePath) -{ - idStr fn(filename); - switch(r_screenshotFormat.GetInteger()) - { - case 1: {// bmp - fn.SetFileExtension("bmp"); - R_WriteBMP(fn.c_str(), data, width, height, comp, flipVertical, basePath); - } - break; - case 2: {// png - fn.SetFileExtension("png"); - R_WritePNG(fn.c_str(), data, width, height, comp, flipVertical, idMath::ClampInt(0, 9, r_screenshotPngCompression.GetInteger()), basePath); - } - break; - case 3: {// jpg - fn.SetFileExtension("jpg"); - R_WriteJPG(fn.c_str(), data, width, height, comp, flipVertical, idMath::ClampInt(1, 100, r_screenshotJpgQuality.GetInteger()), basePath); - } - break; - case 0: // tga - default: - fn.SetFileExtension("tga"); - R_WriteTGA(fn.c_str(), data, width, height, flipVertical); //TODO: 4 comp - break; - } -} +#include "Image_files_ext.cpp" #endif diff --git a/Q3E/src/main/jni/doom3/neo/renderer/Image_files_ext.cpp b/Q3E/src/main/jni/doom3/neo/renderer/Image_files_ext.cpp new file mode 100644 index 000000000..a399ad5ae --- /dev/null +++ b/Q3E/src/main/jni/doom3/neo/renderer/Image_files_ext.cpp @@ -0,0 +1,719 @@ +// basic defines and includes +#define STB_IMAGE_IMPLEMENTATION +#define STBI_NO_HDR +#define STBI_NO_LINEAR +#define STBI_ONLY_JPEG // at least for now, only use it for JPEG +#define STBI_NO_STDIO // images are passed as buffers +#define STBI_ONLY_PNG + +#include "../externlibs/stb/stb_image.h" + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "../externlibs/stb/stb_image_write.h" + +// Load functions +void LoadJPG_stb(const char *filename, unsigned char **pic, int *width, int *height, ID_TIME_T *timestamp) +{ + byte *fbuffer; + int len; + + if (pic) { + *pic = NULL; // until proven otherwise + } + + { + idFile *f; + + f = fileSystem->OpenFileRead(filename); + + if (!f) { + return; + } + + len = f->Length(); + + if (timestamp) { + *timestamp = f->Timestamp(); + } + + if (!pic) { + fileSystem->CloseFile(f); + return; // just getting timestamp + } + + fbuffer = (byte *)Mem_ClearedAlloc(len); + f->Read(fbuffer, len); + fileSystem->CloseFile(f); + } + + int w=0, h=0, comp=0; + byte* decodedImageData = stbi_load_from_memory( fbuffer, len, &w, &h, &comp, STBI_rgb_alpha ); + + Mem_Free( fbuffer ); + + if ( decodedImageData == NULL ) { + common->Warning( "stb_image was unable to load JPG %s : %s\n", + filename, stbi_failure_reason()); + return; + } + + // *pic must be allocated with R_StaticAlloc(), but stb_image allocates with malloc() + // (and as there is no R_StaticRealloc(), #define STBI_MALLOC etc won't help) + // so the decoded data must be copied once + int size = w*h*4; + *pic = (byte *)R_StaticAlloc( size ); + memcpy( *pic, decodedImageData, size ); + *width = w; + *height = h; + // now that decodedImageData has been copied into *pic, it's not needed anymore + stbi_image_free( decodedImageData ); +} + +void LoadPNG(const char *filename, byte **pic, int *width, int *height, ID_TIME_T *timestamp) +{ + + byte *fbuffer; + int len; + + if (pic) { + *pic = NULL; // until proven otherwise + } + + { + idFile *f; + + f = fileSystem->OpenFileRead(filename); + + if (!f) { + return; + } + + len = f->Length(); + + if (timestamp) { + *timestamp = f->Timestamp(); + } + + if (!pic) { + fileSystem->CloseFile(f); + return; // just getting timestamp + } + + fbuffer = (byte *)Mem_ClearedAlloc(len); + f->Read(fbuffer, len); + fileSystem->CloseFile(f); + } + + int w=0, h=0, comp=0; + byte* decodedImageData = stbi_load_from_memory( fbuffer, len, &w, &h, &comp, STBI_rgb_alpha ); + + Mem_Free( fbuffer ); + + if ( decodedImageData == NULL ) { + common->Warning( "stb_image was unable to load PNG %s : %s\n", + filename, stbi_failure_reason()); + return; + } + + // *pic must be allocated with R_StaticAlloc(), but stb_image allocates with malloc() + // (and as there is no R_StaticRealloc(), #define STBI_MALLOC etc won't help) + // so the decoded data must be copied once + int size = w*h*4; + *pic = (byte *)R_StaticAlloc( size ); + memcpy( *pic, decodedImageData, size ); + *width = w; + *height = h; + // now that decodedImageData has been copied into *pic, it's not needed anymore + stbi_image_free( decodedImageData ); +} + +#include "../externlibs/soil/soil_dds_image.h" +void LoadDDS(const char *filename, byte **pic, int *width, int *height, ID_TIME_T *timestamp) +{ + + byte *fbuffer; + int len; + + if (pic) { + *pic = NULL; // until proven otherwise + } + + { + idFile *f; + + f = fileSystem->OpenFileRead(filename); + + if (!f) { + return; + } + + len = f->Length(); + + if (timestamp) { + *timestamp = f->Timestamp(); + } + + if (!pic) { + fileSystem->CloseFile(f); + return; // just getting timestamp + } + + fbuffer = (byte *)Mem_ClearedAlloc(len); + f->Read(fbuffer, len); + fileSystem->CloseFile(f); + } + + int w=0, h=0, comp=0; + byte* decodedImageData = stbi_dds_load_from_memory( fbuffer, len, &w, &h, &comp, STBI_rgb_alpha ); + + Mem_Free( fbuffer ); + + if ( decodedImageData == NULL ) { + common->Warning( "stb_image was unable to load PNG %s : %s\n", + filename, stbi_failure_reason()); + return; + } + + // *pic must be allocated with R_StaticAlloc(), but stb_image allocates with malloc() + // (and as there is no R_StaticRealloc(), #define STBI_MALLOC etc won't help) + // so the decoded data must be copied once + int size = w*h*4; + *pic = (byte *)R_StaticAlloc( size ); + memcpy( *pic, decodedImageData, size ); + *width = w; + *height = h; + // now that decodedImageData has been copied into *pic, it's not needed anymore + stbi_image_free( decodedImageData ); +} + +// Utility +static void R_ImageFlipVertical(byte *data, int width, int height, int comp) +{ + int i; + byte *line1, *line2; + byte *swapBuffer; + int lineSize = width * comp; + + swapBuffer = (byte *)malloc(width * comp); + for (i = 0; i < height / 2; ++i) { + line1 = &data[i * lineSize]; + line2 = &data[(height - i - 1) * lineSize]; + memcpy(swapBuffer, line1, lineSize); + memcpy(line1, line2, lineSize); + memcpy(line2, swapBuffer, lineSize); + } + free(swapBuffer); +} + +#ifdef _HARM_IMAGE_RGBA_TO_RGB +static void R_ImageRGBA8888ToRGB888(const byte *data, int width, int height, byte *to) +{ + int i, j; + const byte *temp; + byte *target; + + for (i = 0 ; i < width ; i++) { + for (j = 0 ; j < height ; j++) { + temp = data + j * 4 * width + i * 4; + target = to + j * 3 * width + i * 3; + memcpy(target, temp, 3); + } + } +} +#endif + +// Save functions +static void R_StbWriteImageFile(void *context, void *data, int size) +{ + idFile *f = (idFile *)context; + f->Write(data, size); +} + +void R_WritePNG(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical, int quality, const char *basePath) +{ + stbi_write_png_compression_level = idMath::ClampInt(0, 9, quality); + byte *ndata = NULL; + if(flipVertical) + { + ndata = (byte *) malloc(width * height * comp); + memcpy(ndata, data, width * height * comp); + R_ImageFlipVertical(ndata, width, height, comp); + data = ndata; + } + if(!basePath) + basePath = "fs_savepath"; + idFile *f = fileSystem->OpenFileWrite(filename, basePath); + if(f) + { + int r = stbi_write_png_to_func(R_StbWriteImageFile, (void *)f, width, height, comp, data, width * comp); + free(ndata); + if(!r) + common->Warning("R_WritePNG fail: %d", r); + fileSystem->CloseFile(f); + } +} + +void R_WriteJPG(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical, int compression, const char *basePath) +{ + byte *ndata = NULL; + if(flipVertical) + { + ndata = (byte *) malloc(width * height * comp); + memcpy(ndata, data, width * height * comp); + R_ImageFlipVertical(ndata, width, height, comp); + data = ndata; + } + if(!basePath) + basePath = "fs_savepath"; + idFile *f = fileSystem->OpenFileWrite(filename, basePath); + if(f) + { + int r = stbi_write_jpg_to_func(R_StbWriteImageFile, (void *)f, width, height, comp, data, idMath::ClampInt(1, 100, compression)); + free(ndata); + if(!r) + common->Warning("R_WriteJPG fail: %d", r); + fileSystem->CloseFile(f); + } +} + +void R_WriteBMP(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical, const char *basePath) +{ + byte *ndata = NULL; + if(flipVertical) + { + ndata = (byte *) malloc(width * height * comp); + memcpy(ndata, data, width * height * comp); + R_ImageFlipVertical(ndata, width, height, comp); + data = ndata; + } + if(!basePath) + basePath = "fs_savepath"; + idFile *f = fileSystem->OpenFileWrite(filename, basePath); + if(f) + { + int r = stbi_write_bmp_to_func(R_StbWriteImageFile, (void *)f, width, height, comp, data); + free(ndata); + if(!r) + common->Warning("R_WriteBMP fail: %d", r); + fileSystem->CloseFile(f); + } +} + +#include "../externlibs/soil/image_dxt.h" +void R_WriteDDS(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical, const char *basePath) +{ + byte *ndata = NULL; + if(flipVertical) + { + ndata = (byte *) malloc(width * height * comp); + memcpy(ndata, data, width * height * comp); + R_ImageFlipVertical(ndata, width, height, comp); + data = ndata; + } + if(!basePath) + basePath = "fs_savepath"; + idFile *f = fileSystem->OpenFileWrite(filename, basePath); + if(f) + { + int r = soil_save_image_as_func(R_StbWriteImageFile, (void *)f, width, height, comp, data); + free(ndata); + if(!r) + common->Warning("R_WriteDDS fail: %d", r); + fileSystem->CloseFile(f); + } +} + +void R_WriteScreenshotImage(const char *filename, const byte *data, int width, int height, int comp, bool flipVertical, const char *basePath) +{ + idStr fn(filename); + switch(r_screenshotFormat.GetInteger()) + { + case 1: {// bmp + fn.SetFileExtension("bmp"); + R_WriteBMP(fn.c_str(), data, width, height, comp, flipVertical, basePath); + } + break; + case 2: {// png + fn.SetFileExtension("png"); + R_WritePNG(fn.c_str(), data, width, height, comp, flipVertical, idMath::ClampInt(0, 9, r_screenshotPngCompression.GetInteger()), basePath); + } + break; + case 3: {// jpg + fn.SetFileExtension("jpg"); + R_WriteJPG(fn.c_str(), data, width, height, comp, flipVertical, idMath::ClampInt(1, 100, r_screenshotJpgQuality.GetInteger()), basePath); + } + break; + case 4: {// dds + fn.SetFileExtension("dds"); + R_WriteDDS(fn.c_str(), data, width, height, comp, flipVertical, basePath); + } + break; + case 0: // tga + default: + fn.SetFileExtension("tga"); + R_WriteTGA(fn.c_str(), data, width, height, flipVertical); //TODO: 4 comp + break; + } +} + +// Convert image +bool R_ConvertImage(const char *filename, const char *toFormat, idStr &ret, int comp = 4, int compression = -1, bool flipVertical = false, bool makePowerOf2 = false, const char *basePath = NULL, int *rwidth = NULL, int *rheight = NULL) +{ + idStr fileNameStr(filename); + idStr srcExt; + int width = 0, height = 0; + byte *pic = NULL; + ID_TIME_T timestamp; + bool res; + + if(rwidth) + *rwidth = -1; + if(rheight) + *rheight = -1; + + // PreCheck + fileNameStr.ExtractFileExtension(srcExt); + if(srcExt.IsEmpty()) + { + ret = idStr("Can not get source image extension: ") + filename; + return false; + } + if(!idStr::Icmp(srcExt, toFormat)) + { + ret = idStr("Source image extension same as target: ") + srcExt; + return false; + } + + // Load + if(!idStr::Icmp(srcExt, "tga")) + { + LoadTGA(filename, &pic, &width, &height, ×tamp); + } + else if(!idStr::Icmp(srcExt, "jpg") || !idStr::Icmp(srcExt, "jpeg") ) + { + LoadJPG(filename, &pic, &width, &height, ×tamp); + } + else if (!idStr::Icmp(srcExt, "pcx")) + { + LoadPCX32(filename, &pic, &width, &height, ×tamp); + } + else if(!idStr::Icmp(srcExt, "png")) + { + LoadPNG(filename, &pic, &width, &height, ×tamp); + } + else if(!idStr::Icmp(srcExt, "dds")) + { + LoadDDS(filename, &pic, &width, &height, ×tamp); + } + else if(!idStr::Icmp(srcExt, "bmp")) + { + LoadBMP(filename, &pic, &width, &height, ×tamp); + } + else + { + ret = idStr("Unsupport source image format: ") + srcExt; + return false; + } + + // Check load + if ((width < 1) || (height < 1)) { + if (pic) { + R_StaticFree(pic); + ret = idStr("Unable get source image size"); + return false; + } + } + + if ((!pic) || (timestamp == -1)) { + if (pic) { + R_StaticFree(pic); + } + ret = idStr("Unable get source image data"); + return false; + } + + // + // convert to exact power of 2 sizes + // + if (pic && makePowerOf2) { + int w, h; + int scaled_width, scaled_height; + byte *resampledBuffer; + + w = width; + h = height; + + for (scaled_width = 1 ; scaled_width < w ; scaled_width<<=1) + ; + + for (scaled_height = 1 ; scaled_height < h ; scaled_height<<=1) + ; + + if (scaled_width != w || scaled_height != h) { + if (globalImages->image_roundDown.GetBool() && scaled_width > w) { + scaled_width >>= 1; + } + + if (globalImages->image_roundDown.GetBool() && scaled_height > h) { + scaled_height >>= 1; + } + + resampledBuffer = R_ResampleTexture(pic, w, h, scaled_width, scaled_height); + R_StaticFree(pic); + pic = resampledBuffer; + width = scaled_width; + height = scaled_height; + } + } + + // Save + idStr targetPath; + if(basePath && basePath[0]) + { + targetPath = basePath; + targetPath.AppendPath(filename); + } + else + { + targetPath = filename; + } + targetPath.SetFileExtension(toFormat); + res = false; + + if(!idStr::Icmp(toFormat, "tga")) + { + R_WriteTGA(targetPath.c_str(), pic, width, height, flipVertical); + res = true; + } + else if(!idStr::Icmp(toFormat, "jpg") || !idStr::Icmp(toFormat, "jpeg") ) + { + if(compression < 0 || compression > 100) + compression = 100; + R_WriteJPG(targetPath.c_str(), pic, width, height, comp, flipVertical, compression); + res = true; + } + else if(!idStr::Icmp(toFormat, "png")) + { + if(compression < 0 || compression > 9) + compression = 9; + R_WritePNG(targetPath.c_str(), pic, width, height, comp, flipVertical, compression); + res = true; + } + else if(!idStr::Icmp(toFormat, "dds")) + { + R_WriteDDS(targetPath.c_str(), pic, width, height, comp, flipVertical); + res = true; + } + else if(!idStr::Icmp(toFormat, "bmp")) + { + R_WriteBMP(targetPath.c_str(), pic, width, height, comp, flipVertical); + res = true; + } + else + { + ret = idStr("Unsupport target image format: ") + toFormat; + } + + // Free + if (pic) + R_StaticFree(pic); + if(res) + { + ret = targetPath; + if(rwidth) + *rwidth = width; + if(rheight) + *rheight = height; + } + + return true; +} + +// Command functions +/* + * convertImage [ + * --directory= + * -d + * + * --flipVertical + * -f + * + * --compression= + * -q + * + * --makePowerOf2 + * -p + * + * --component= + * -c + * ] + */ +void R_ConvertImage_f(const idCmdArgs &args) +{ + if(args.Argc() < 3) + { + common->Printf("Usage: convertImage [\n" + "\t-d --directory= \n" + "\t-f --flipVertical \n" + "\t-q --compression= \n" + "\t-c --component= \n" + "\t-p --makePowerOf2 \n" + "]"); + return; + } + + const char *filename = args.Argv(1); + const char *toFormat = args.Argv(2); + bool flipVertical = false; + bool makePowerOf2 = false; + int compression = -1; + int component = 4; + idStr basePath; + + int width; + int height; + idStr ret; + + for(int i = 3; i < args.Argc(); i++) + { + idStr arg = args.Argv(i); + int argType = 0; + if(!arg.Cmp("-")) + { + i++; + if(i >= args.Argc()) + { + common->Warning("Missing short argument"); + break; + } + arg = args.Argv(i); + argType = 1; + } + else if(!arg.Cmp("--")) + { + i++; + if(i >= args.Argc()) + { + common->Warning("Missing long argument"); + break; + } + arg = args.Argv(i); + argType = 2; + } + +#define PARSE_LONG_ARG(what) \ + if(argType != 2) \ + { \ + common->Warning("Required long argument"); \ + continue; \ + } \ + i++; \ + if(i >= args.Argc()) \ + { \ + common->Warning("Missing long argument `=` token"); \ + break; \ + } \ + arg = args.Argv(i); \ + if(arg != "=") \ + { \ + common->Warning("Expected long argument `=` token"); \ + continue; \ + } \ + i++; \ + if(i >= args.Argc()) \ + { \ + common->Warning("Missing long argument `" what "` value"); \ + break; \ + } \ + arg = args.Argv(i); + +#define PARSE_SHORT_ARG(what) \ + if(argType != 1) \ + { \ + common->Warning("Required short argument"); \ + continue; \ + } \ + i++; \ + if(i >= args.Argc()) \ + { \ + common->Warning("Missing short argument `" what "` value"); \ + break; \ + } \ + arg = args.Argv(i); + + if(arg == "directory") + { + PARSE_LONG_ARG("directory"); + basePath = arg; + } + else if(arg == "d") + { + PARSE_SHORT_ARG("d"); + basePath = arg; + } + else if(arg == "flipVertical" || arg == "f") + { + flipVertical = true; + } + else if(arg == "makePowerOf2" || arg == "p") + { + makePowerOf2 = true; + } + else if(arg == "compression") + { + PARSE_LONG_ARG("compression"); + compression = atoi(arg.c_str()); + } + else if(arg == "q") + { + PARSE_SHORT_ARG("q"); + compression = atoi(arg.c_str()); + } + else if(arg == "component") + { + PARSE_LONG_ARG("component"); + component = atoi(arg.c_str()); + } + else if(!arg.Cmp("c")) + { + PARSE_SHORT_ARG("c"); + component = atoi(arg.c_str()); + } + else + { + common->Warning("Unknown argument: %s", arg.c_str()); + } + } + +#undef PARSE_LONG_ARG +#undef PARSE_SHORT_ARG + + common->Printf("ConvertImage: %s -> %s\n", filename, toFormat); + common->Printf("\tdirectory: %s\n", basePath.c_str()); + common->Printf("\tflip vertical: %s\n", flipVertical ? "ON" : "OFF"); + common->Printf("\tmake power of 2: %s\n", makePowerOf2 ? "ON" : "OFF"); + common->Printf("\tcompression: %d\n", compression); + common->Printf("\tcomponent: %d\n", component); + + if(!idStr::Icmp(toFormat, "png")) + { + if(compression < 0 || compression > 9) + compression = 9; + } + else if(!idStr::Icmp(toFormat, "jpg") || !idStr::Icmp(toFormat, "jpeg")) + { + if(compression < 0 || compression > 100) + compression = 100; + } + if(component != 3 && component != 4) + component = 4; + + if(R_ConvertImage(filename, toFormat, ret, component, compression, flipVertical, makePowerOf2, basePath, &width, &height)) + { + common->Printf("Target image save to %s(%d x %d)\n", ret.c_str(), width, height); + } + else + { + common->Printf("Convert error: %s\n", ret.c_str()); + } +} \ No newline at end of file diff --git a/Q3E/src/main/jni/doom3/neo/renderer/Model_md5.cpp b/Q3E/src/main/jni/doom3/neo/renderer/Model_md5.cpp index eea5bc099..38d5b3a55 100644 --- a/Q3E/src/main/jni/doom3/neo/renderer/Model_md5.cpp +++ b/Q3E/src/main/jni/doom3/neo/renderer/Model_md5.cpp @@ -1094,3 +1094,5 @@ int idRenderModelMD5::GetSurfaceMask(const char *name) const return 0; } #endif + +#include "Model_md5anim.cpp" \ No newline at end of file diff --git a/Q3E/src/main/jni/doom3/neo/renderer/Model_md5anim.cpp b/Q3E/src/main/jni/doom3/neo/renderer/Model_md5anim.cpp new file mode 100644 index 000000000..3082042ae --- /dev/null +++ b/Q3E/src/main/jni/doom3/neo/renderer/Model_md5anim.cpp @@ -0,0 +1,860 @@ +namespace md5anim +{ + typedef struct baseframe_s + { + idList transforms; + + idStr ToString(void) const; + bool Parse(idLexer &parser, int numJoints); + } baseframe_t; + + typedef struct frame_s + { + int index; + idList transforms; + + idStr ToString(void) const; + bool Parse(idLexer &parser, int numJoints); + } frame_t; + + typedef struct hierarchy_s + { + idList joints; + + idStr ToString(void) const; + bool Parse(idLexer &parser, int numJoints); + } hierarchy_t; + + typedef struct bounds_s + { + idList frames; + + idStr ToString(void) const; + bool Parse(idLexer &parser, int numFrames); + } bounds_t; + + class idMD5AnimFile + { + private: + int numFrames; + int numJoints; + int frameRate; + int numAnimatedComponents; + hierarchy_t hierarchy; + bounds_t bounds; + baseframe_t baseframe; + idList frames; + idStr name; + + public: + idMD5AnimFile(); + + bool LoadAnim(const char *filename); + bool AppendAnim(const idMD5AnimFile &other, int startFrame = 0, int endFrame = -1); + bool CutAnim(int startFrame = 0, int endFrame = -1); + bool ReverseAnim(int startFrame = 0, int endFrame = -1); + idStr ToString(void) const; + int NumFrames() const { + return numFrames; + } + + private: + bool Check(void) const; + bool Compat(const idMD5AnimFile &other) const; + void NormalizedFrame(int &frame) const; + }; + + idStr ReadLine(idLexer &parser) + { + idToken token; + parser.ReadToken(&token); + idStr str; + if(token.type == TT_STRING) + str.Append("\""); + str.Append(token); + if(token.type == TT_STRING) + str.Append("\""); + parser.ReadRestOfLine(str); + str.Strip(' '); + // common->Printf(">> %s\n", str.c_str()); + return str; + } + + int WriteText(const idStr &text, idFile *file) + { + return file->Write(text.c_str(), text.Length()); + } + + idMD5AnimFile::idMD5AnimFile() + : numFrames(0), + numJoints(0), + frameRate(0), + numAnimatedComponents(0) + { + } + + void idMD5AnimFile::NormalizedFrame(int &frame) const + { + if(frame < 0) + frame = numFrames + frame; + if(frame < 0) + frame = 0; + else if(frame >= numFrames) + frame = numFrames - 1; + } + + bool idMD5AnimFile::LoadAnim(const char *filename) + { + int version; + idLexer parser(LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS | LEXFL_NOSTRINGCONCAT + | LEXFL_NOFATALERRORS); + idToken token; + int i; + + if (!parser.LoadFile(filename)) { + return false; + } + + name = filename; + + parser.ExpectTokenString(MD5_VERSION_STRING); + version = parser.ParseInt(); + + if (version != MD5_VERSION) { + parser.Error("Invalid version %d. Should be version %d\n", version, MD5_VERSION); + return false; + } + + // skip the commandline + parser.ExpectTokenString("commandline"); + parser.ReadToken(&token); + + // parse num frames + parser.ExpectTokenString("numFrames"); + numFrames = parser.ParseInt(); + + if (numFrames <= 0) { + parser.Error("Invalid number of frames: %d", numFrames); + return false; + } + + // parse num joints + parser.ExpectTokenString("numJoints"); + numJoints = parser.ParseInt(); + + if (numJoints <= 0) { + parser.Error("Invalid number of joints: %d", numJoints); + return false; + } + + // parse frame rate + parser.ExpectTokenString("frameRate"); + frameRate = parser.ParseInt(); + + if (frameRate < 0) { + parser.Error("Invalid frame rate: %d", frameRate); + return false; + } + + // parse number of animated components + parser.ExpectTokenString("numAnimatedComponents"); + numAnimatedComponents = parser.ParseInt(); + + if ((numAnimatedComponents < 0) || (numAnimatedComponents > numJoints * 6)) { + parser.Error("Invalid number of animated components: %d", numAnimatedComponents); + return false; + } + + // parse the hierarchy + hierarchy.Parse(parser, numJoints); + + // parse bounds + bounds.Parse(parser, numFrames); + + // parse base frame + baseframe.Parse(parser, numJoints); + + // parse frames + frames.SetGranularity(1); + frames.SetNum(numFrames); + + for (i = 0; i < numFrames; i++) { + frame_t &frame = frames[i]; + frame.Parse(parser, numJoints); + + if (frame.index != i) { + parser.Error("Expected frame number %d", i); + return false; + } + } + + /*idStr str = ToString(); + Sys_Printf("%s\n", str.c_str());*/ + // done + return Check(); + } + + bool idMD5AnimFile::Check(void) const + { + if(numFrames <= 0) + { + common->Warning("numFrames <= 0: %d", numFrames); + return false; + } + if(numJoints <= 0) + { + common->Warning("numJoints <= 0: %d", numJoints); + return false; + } + if(frameRate <= 0) + { + common->Warning("frameRate <= 0: %d", frameRate); + return false; + } + if(numAnimatedComponents <= 0) + { + common->Warning("numAnimatedComponents <= 0: %d", numAnimatedComponents); + return false; + } + if(numAnimatedComponents != numJoints * 6) + { + common->Warning("numAnimatedComponents != numJoints * numFrames: %d != %d * 6", numAnimatedComponents, numJoints); + return false; + } + + if(bounds.frames.Num() != numFrames) + { + common->Warning("bounds.Num() != numFrames: %d != %d", bounds.frames.Num(), numFrames); + return false; + } + if(hierarchy.joints.Num() != numJoints) + { + common->Warning("hierarchy.Num() != numJoints: %d != %d", hierarchy.joints.Num(), numJoints); + return false; + } + if(baseframe.transforms.Num() != numJoints) + { + common->Warning("baseframe.Num() != numJoints: %d != %d", baseframe.transforms.Num(), numJoints); + return false; + } + if(frames.Num() != numFrames) + { + common->Warning("frames.Num() != numFrames: %d != %d", frames.Num(), numFrames); + return false; + } + + for(int i = 0; i < numFrames; i++) + { + const frame_t &frame = frames[i]; + if(frame.index != i) + { + common->Warning("frames.index != index: %d != %d", frame.index, i); + return false; + } + if(frame.transforms.Num() != numJoints) + { + common->Warning("frames.Num() != numJoints: %d != %d", frame.transforms.Num(), numJoints); + return false; + } + } + return true; + } + + bool idMD5AnimFile::Compat(const idMD5AnimFile &other) const + { + if(!other.Check()) + return false; + + if(numJoints != other.numJoints) + { + common->Warning("numJoints != numJoints: %d != %d", numJoints, other.numJoints); + return false; + } + + return true; + } + + bool idMD5AnimFile::AppendAnim(const idMD5AnimFile &other, int startFrame, int endFrame) + { + if(numJoints == 0) + { + numJoints = other.numJoints; + frameRate = other.frameRate; + numAnimatedComponents = other.numAnimatedComponents; + hierarchy = other.hierarchy; + baseframe = other.baseframe; + } + if(!Compat(other)) + return false; + + other.NormalizedFrame(startFrame); + other.NormalizedFrame(endFrame); + + if(startFrame > endFrame) + return false; + + for(int i = startFrame; i <= endFrame; i++) + { + frame_t frame = other.frames[i]; + frame.index = this->numFrames; + frames.Append(frame); + + idStr b = other.bounds.frames[i]; + bounds.frames.Append(b); + + this->numFrames++; + } + + return true; + } + + bool idMD5AnimFile::CutAnim(int startFrame, int endFrame) + { + NormalizedFrame(startFrame); + NormalizedFrame(endFrame); + + if(startFrame == 0 && endFrame == numFrames - 1) + return true; + if(startFrame > endFrame) + return false; + + int diff = endFrame - startFrame + 1; + + idList target; + bounds_t b; + target.SetGranularity(1); + target.SetNum(diff); + b.frames.SetGranularity(1); + b.frames.SetNum(diff); + for(int i = startFrame; i <= endFrame; i++) + { + int index = i - startFrame; + target[index] = frames[i]; + target[index].index = index; + b.frames[i - startFrame] = bounds.frames[i]; + } + + this->frames = target; + this->bounds = b; + this->numFrames = diff; + + return Check(); + } + + bool idMD5AnimFile::ReverseAnim(int startFrame, int endFrame) + { + NormalizedFrame(startFrame); + NormalizedFrame(endFrame); + + if(startFrame >= endFrame) + return false; + + int diff = endFrame - startFrame + 1; + diff /= 2; + + for(int i = 0; i < diff; i++) + { + int a = startFrame + i; + int b = endFrame - i; + int indexA = frames[a].index; + int indexB = frames[b].index; + frame_t f = frames[a]; + frames[a] = frames[b]; + frames[b] = f; + frames[a].index = indexA; + frames[b].index = indexB; + idStr bo = bounds.frames[a]; + bounds.frames[a] = bounds.frames[b]; + bounds.frames[b] = bo; + } + + return Check(); + } + + idStr idMD5AnimFile::ToString(void) const + { + idStr str; + + str.Append(va("MD5Version %d \n", MD5_VERSION)); + str.Append("commandline \"\"\n"); + str.Append("\n"); + str.Append(va("numFrames %d \n", numFrames)); + str.Append(va("numJoints %d \n", numJoints)); + str.Append(va("frameRate %d \n", frameRate)); + str.Append(va("numAnimatedComponents %d \n", numAnimatedComponents)); + str.Append("\n"); + str.Append(hierarchy.ToString()); + str.Append("\n"); + str.Append(bounds.ToString()); + str.Append("\n"); + str.Append(baseframe.ToString()); + str.Append("\n"); + for(int i = 0; i < frames.Num(); i++) + { + str.Append(frames[i].ToString()); + str.Append("\n"); + } + + return str; + } + + bool baseframe_s::Parse(idLexer &parser, int numJoints) + { + idStr str; + + transforms.SetGranularity(1); + transforms.SetNum(numJoints); + + parser.ExpectTokenString("baseframe"); + parser.ExpectTokenString("{"); + + for (int i = 0; i < numJoints; i++) { + idStr text = ReadLine(parser); + if(text.IsEmpty()) + return false; + transforms[i] = text; + } + + parser.ExpectTokenString("}"); + + return !parser.HadError(); + } + + idStr baseframe_s::ToString(void) const + { + idStr str; + + str.Append("baseframe {\n"); + for(int i = 0; i < transforms.Num(); i++) + { + str.Append(" "); + str.Append(transforms[i]); + str.Append("\n"); + } + str.Append("}\n"); + + return str; + } + + bool frame_s::Parse(idLexer &parser, int numJoints) + { + idStr str; + + transforms.SetGranularity(1); + transforms.SetNum(numJoints); + + parser.ExpectTokenString("frame"); + index = parser.ParseInt(); + parser.ExpectTokenString("{"); + + for (int i = 0; i < numJoints; i++) { + idStr text = ReadLine(parser); + if(text.IsEmpty()) + return false; + transforms[i] = text; + } + + parser.ExpectTokenString("}"); + + return !parser.HadError(); + } + + idStr frame_s::ToString(void) const + { + idStr str; + + str.Append(va("frame %d {\n", index)); + for(int i = 0; i < transforms.Num(); i++) + { + str.Append(" "); + str.Append(transforms[i]); + str.Append("\n"); + } + str.Append("}\n"); + + return str; + } + + bool hierarchy_s::Parse(idLexer &parser, int numJoints) + { + idStr str; + + joints.SetGranularity(1); + joints.SetNum(numJoints); + + parser.ExpectTokenString("hierarchy"); + parser.ExpectTokenString("{"); + + for (int i = 0; i < numJoints; i++) { + idStr text = ReadLine(parser); + if(text.IsEmpty()) + return false; + joints[i] = text; + } + + parser.ExpectTokenString("}"); + + return !parser.HadError(); + } + + idStr hierarchy_s::ToString(void) const + { + idStr str; + + str.Append("hierarchy {\n"); + for(int i = 0; i < joints.Num(); i++) + { + str.Append(" "); + str.Append(joints[i]); + str.Append("\n"); + } + str.Append("}\n"); + + return str; + } + + bool bounds_s::Parse(idLexer &parser, int numFrames) + { + idStr str; + + parser.ExpectTokenString("bounds"); + parser.ExpectTokenString("{"); + frames.SetGranularity(1); + frames.SetNum(numFrames); + + for (int i = 0; i < numFrames; i++) { + idStr text = ReadLine(parser); + if(text.IsEmpty()) + return false; + frames[i] = text; + } + + parser.ExpectTokenString("}"); + + return !parser.HadError(); + } + + idStr bounds_s::ToString(void) const + { + idStr str; + + str.Append("bounds {\n"); + for(int i = 0; i < frames.Num(); i++) + { + str.Append(" "); + str.Append(frames[i]); + str.Append("\n"); + } + str.Append("}\n"); + + return str; + } +} + +static void R_CutAnim_f(const idCmdArgs &args) +{ + if (args.Argc() < 4) + { + common->Printf("Usage: cutAnim "); + return; + } + + md5anim::idMD5AnimFile file; + const char *filename = args.Argv(1); + if(!file.LoadAnim(filename)) + { + common->Warning("Load md5anim file error: %s", filename); + return; + } + + const char *start = args.Argv(2); + const char *end = args.Argv(3); + if(!file.CutAnim(atoi(start), atoi(end))) + { + common->Warning("Cut md5anim error: %s(%s - %s)", filename, start, end); + return; + } + + idStr text = file.ToString(); + idStr basePath; + + idStr targetPath; + if(!basePath.IsEmpty()) + { + targetPath = basePath; + targetPath.AppendPath(filename); + } + else + { + targetPath = filename; + targetPath.StripFileExtension(); + targetPath.Append(va("_cut_%s_%s", start, end)); + targetPath.Append(".md5anim"); + } + idFile *f = fileSystem->OpenFileWrite(targetPath.c_str(), "fs_savepath"); + md5anim::WriteText(text, f); + fileSystem->CloseFile(f); + + common->Printf("Target md5anim save to %s(frames: %d)\n", targetPath.c_str(), file.NumFrames()); +} + +static void R_ReverseAnim_f(const idCmdArgs &args) +{ + if (args.Argc() < 2) + { + common->Printf("Usage: reverseAnim [ ]"); + return; + } + + md5anim::idMD5AnimFile file; + const char *filename = args.Argv(1); + if(!file.LoadAnim(filename)) + { + common->Warning("Load md5anim file error: %s", filename); + return; + } + + int startFrame = 0; + int endFrame = -1; + if(args.Argc() > 2) + startFrame = atoi(args.Argv(2)); + if(args.Argc() > 3) + endFrame = atoi(args.Argv(3)); + if(!file.ReverseAnim(startFrame, endFrame)) + { + common->Warning("Reverse md5anim error: %s(%d - %d)", filename, startFrame, endFrame); + return; + } + + idStr text = file.ToString(); + idStr basePath; + + idStr targetPath; + if(!basePath.IsEmpty()) + { + targetPath = basePath; + targetPath.AppendPath(filename); + } + else + { + targetPath = filename; + targetPath.StripFileExtension(); + targetPath.Append(va("_reverse_%d_%d", startFrame, endFrame)); + targetPath.Append(".md5anim"); + } + idFile *f = fileSystem->OpenFileWrite(targetPath.c_str(), "fs_savepath"); + md5anim::WriteText(text, f); + fileSystem->CloseFile(f); + + common->Printf("Target md5anim save to %s(frames: %d)\n", targetPath.c_str(), file.NumFrames()); +} + +static void R_LinkAnim_f(const idCmdArgs &args) +{ + if (args.Argc() < 5) + { + common->Printf("Usage: cutAnim [-a [-f] ......]"); + return; + } + +#define PARSE_SHORT_ARG(what) \ + if(argType != 1) \ + { \ + common->Warning("Required short argument"); \ + continue; \ + } \ + i++; \ + if(i >= args.Argc()) \ + { \ + readNum = -1; \ + common->Warning("Missing short argument `" what "` value"); \ + break; \ + } \ + arg = args.Argv(i); + + struct animPart_s + { + idStr file_name; + int start; + int end; + }; + struct animLoad_s + { + idStr file_name; + md5anim::idMD5AnimFile anim; + }; + + idList parts; + idList loadeds; + + animPart_s part; + bool partSeted = false; + int readNum = -1; + for(int i = 2; i < args.Argc(); i++) + { + idStr arg = args.Argv(i); + int argType = 0; + if(!arg.Cmp("-")) + { + i++; + if(i >= args.Argc()) + { + common->Warning("Missing short argument"); + break; + } + arg = args.Argv(i); + argType = 1; + } + + if(argType == 1) + { + if(partSeted) + { + parts.Append(part); + part.start = 0; + part.end = -1; + readNum = 0; + } + + if(arg == "a") + { + PARSE_SHORT_ARG("a"); + part.file_name = arg; + part.start = 0; + part.end = -1; + partSeted = true; + readNum = 0; + } + else if(arg == "f") + { + PARSE_SHORT_ARG("f"); + if(!partSeted) + { + common->Warning("Missing animation file"); + continue; + } + part.start = part.end = atoi(arg.c_str()); + readNum = 1; + } + else + { + readNum = -1; + partSeted = false; + common->Warning("Missing short argument"); + continue; + } + } + else + { + if(!partSeted) + { + common->Warning("Missing animation file"); + continue; + } + if(readNum == 0) + { + part.start = part.end = atoi(arg.c_str()); + readNum++; + } + else if(readNum == 1) + { + part.end = atoi(arg.c_str()); + readNum++; + } + else + { + readNum = -1; + partSeted = false; + common->Warning("More frame index: %s", arg.c_str()); + continue; + } + } + } + if(readNum >= 0) + parts.Append(part); + + for(int i = 0; i < parts.Num(); i++) + { + const animPart_s &part = parts[i]; + common->Printf("Link %d: %s(%d - %d)\n", i, part.file_name.c_str(), part.start, part.end); + + // check need cache + bool exists = false; + for(int m = 0; m < loadeds.Num(); m++) + { + if(loadeds[i].file_name == part.file_name) + { + exists = true; + break; + } + } + // cache + if(!exists) + { + animLoad_s loaded; + if(!loaded.anim.LoadAnim(part.file_name)) + { + common->Warning("Load md5anim file error: %s", part.file_name.c_str()); + return; + } + loaded.file_name = part.file_name; + loadeds.Append(loaded); + } + } + + md5anim::idMD5AnimFile file; + for(int i = 0; i < parts.Num(); i++) + { + const animPart_s &part = parts[i]; + const md5anim::idMD5AnimFile *f = NULL; + for(int m = 0; m < loadeds.Num(); m++) + { + if(loadeds[i].file_name == part.file_name) + { + f = &loadeds[i].anim; + break; + } + } + if(!file.AppendAnim(*f, part.start, part.end)) + { + common->Warning("Append md5anim error: %d - %s(%d - %d)", i, part.file_name.c_str(), part.start, part.end); + return; + } + } + + idStr text = file.ToString(); + idStr basePath; + idStr filename = args.Argv(1); + if(filename.Length() < strlen(".md5anim") || idStr::Icmp(filename.Right(strlen(".md5anim")), ".md5anim")) + filename.Append(".md5anim"); + + idStr targetPath; + if(!basePath.IsEmpty()) + { + targetPath = basePath; + targetPath.AppendPath(filename); + } + else + { + targetPath = filename; + } + idFile *f = fileSystem->OpenFileWrite(targetPath.c_str(), "fs_savepath"); + md5anim::WriteText(text, f); + fileSystem->CloseFile(f); + + common->Printf("Target md5anim save to %s(frames: %d)\n", targetPath.c_str(), file.NumFrames()); + +#undef PARSE_SHORT_ARG +} + +static void ArgCompletion_AnimName(const idCmdArgs &args, void(*callback)(const char *s)) +{ + cmdSystem->ArgCompletion_FolderExtension(args, callback, "models/", false, ".md5anim", NULL); +} + +void MD5Anim_AddCommand(void) +{ + cmdSystem->AddCommand("cutAnim", R_CutAnim_f, CMD_FL_RENDERER, "cut md5 anim", ArgCompletion_AnimName); + cmdSystem->AddCommand("reverseAnim", R_ReverseAnim_f, CMD_FL_RENDERER, "reverse md5 anim", ArgCompletion_AnimName); + cmdSystem->AddCommand("linkAnim", R_LinkAnim_f, CMD_FL_RENDERER, "link md5 anim", ArgCompletion_AnimName); +} \ No newline at end of file diff --git a/Q3E/src/main/jni/doom3/neo/renderer/RenderSystem_init.cpp b/Q3E/src/main/jni/doom3/neo/renderer/RenderSystem_init.cpp index cf198e2a8..7e8eced15 100644 --- a/Q3E/src/main/jni/doom3/neo/renderer/RenderSystem_init.cpp +++ b/Q3E/src/main/jni/doom3/neo/renderer/RenderSystem_init.cpp @@ -218,6 +218,7 @@ idCVar r_debugRenderToTexture("r_debugRenderToTexture", "0", CVAR_RENDERER | CVA idCVar harm_r_maxFps( "harm_r_maxFps", "0", CVAR_RENDERER | CVAR_INTEGER | CVAR_ARCHIVE, "Limit maximum FPS. 0 = unlimited" ); idCVar harm_r_shadowCarmackInverse("harm_r_shadowCarmackInverse", "0", CVAR_INTEGER|CVAR_RENDERER|CVAR_ARCHIVE, "[Harmattan]: Stencil shadow using Carmack-Inverse."); +idCVar r_scaleMenusTo43( "r_scaleMenusTo43", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_BOOL, "Scale menus, fullscreen videos and PDA to 4:3 aspect ratio" ); //k: temp memory allocate in stack / heap control on Android #ifdef _DYNAMIC_ALLOC_STACK_OR_HEAP // #warning "For fix `DOOM3: The lost mission` mod, when load `game/le_hell` map(loading resource `models/mapobjects/hell/hellintro.lwo` model, a larger scene, alloca() stack out of memory)." @@ -225,7 +226,7 @@ idCVar harm_r_shadowCarmackInverse("harm_r_shadowCarmackInverse", "0", CVAR_INTE #endif #ifdef _USING_STB -idCVar r_screenshotFormat("r_screenshotFormat", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "Screenshot format. 0 = TGA (default), 1 = BMP, 2 = PNG, 3 = JPG", 0, 3, idCmdSystem::ArgCompletion_Integer<0, 3>); +idCVar r_screenshotFormat("r_screenshotFormat", "0", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "Screenshot format. 0 = TGA (default), 1 = BMP, 2 = PNG, 3 = JPG, 4 = DDS", 0, 4, idCmdSystem::ArgCompletion_Integer<0, 4>); idCVar r_screenshotJpgQuality("r_screenshotJpgQuality", "75", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "Screenshot quality for JPG images (0-100)", 0, 100, idCmdSystem::ArgCompletion_Integer<0, 100>); idCVar r_screenshotPngCompression("r_screenshotPngCompression", "3", CVAR_RENDERER | CVAR_ARCHIVE | CVAR_INTEGER, "Compression level when using PNG screenshots (0-9)", 0, 9, idCmdSystem::ArgCompletion_Integer<0, 9>); #endif @@ -1382,6 +1383,13 @@ void idRenderSystemLocal::TakeScreenshot(int width, int height, const char *file R_WriteJPG(fn.c_str(), buffer + 18, width, height, 4, true, idMath::ClampInt(0, 9, idMath::ClampInt(1, 100, r_screenshotJpgQuality.GetInteger())), basePath); } break; + case 4: {// dds + idStr fn(fileName); + fn.SetFileExtension("dds"); + const char *basePath = strstr(fileName, "viewnote") ? "fs_cdpath" : NULL; + R_WriteDDS(fn.c_str(), buffer + 18, width, height, 4, true, basePath); + } + break; case 0: // tga default: #endif @@ -2243,6 +2251,12 @@ void R_InitCommands(void) extern void R_DumpShadowMap_f(const idCmdArgs &args); cmdSystem->AddCommand("harm_dumpShadowMap", R_DumpShadowMap_f, CMD_FL_RENDERER, "dump shadow map to file in next frame"); #endif +#ifdef _USING_STB + extern void R_ConvertImage_f(const idCmdArgs &args); + cmdSystem->AddCommand("convertImage", R_ConvertImage_f, CMD_FL_RENDERER, "convert image format", idCmdSystem::ArgCompletion_ImageName); +#endif + extern void MD5Anim_AddCommand(void); + MD5Anim_AddCommand(); } /* diff --git a/Q3E/src/main/jni/doom3/neo/renderer/draw_glsl_shadowmapping.cpp b/Q3E/src/main/jni/doom3/neo/renderer/draw_glsl_shadowmapping.cpp index f0cd8e4e1..7a0424b2d 100644 --- a/Q3E/src/main/jni/doom3/neo/renderer/draw_glsl_shadowmapping.cpp +++ b/Q3E/src/main/jni/doom3/neo/renderer/draw_glsl_shadowmapping.cpp @@ -397,7 +397,7 @@ void R_SaveColorBuffer(const char *name) //GL_CheckErrors("glReadPixels"); #ifdef _USING_STB - R_WriteImage(name, data, width, height, 4, true); + R_WriteScreenshotImage(name, data, width, height, 4, true); #else R_WriteTGA(name, data, width, height, false); #endif diff --git a/Q3E/src/main/jni/doom3/neo/ui/DeviceContext.cpp b/Q3E/src/main/jni/doom3/neo/ui/DeviceContext.cpp index 6a9075473..44f5a0ab5 100644 --- a/Q3E/src/main/jni/doom3/neo/ui/DeviceContext.cpp +++ b/Q3E/src/main/jni/doom3/neo/ui/DeviceContext.cpp @@ -188,6 +188,10 @@ void idDeviceContext::Init() mat.Identity(); origin.Zero(); initialized = true; + + // DG: this is used for the "make sure menus are rendered as 4:3" hack + fixScaleForMenu.Set(1, 1); + fixOffsetForMenu.Set(0, 0); } void idDeviceContext::Shutdown() @@ -324,18 +328,36 @@ void idDeviceContext::AdjustCoords(float *x, float *y, float *w, float *h) { if (x) { *x *= xScale; + if(r_scaleMenusTo43.GetBool()) + { + *x *= fixScaleForMenu.x; // DG: for "render menus as 4:3" hack + *x += fixOffsetForMenu.x; + } } if (y) { *y *= yScale; + if(r_scaleMenusTo43.GetBool()) + { + *y *= fixScaleForMenu.y; // DG: for "render menus as 4:3" hack + *y += fixOffsetForMenu.y; + } } if (w) { *w *= xScale; + if(r_scaleMenusTo43.GetBool()) + { + *w *= fixScaleForMenu.x; // DG: for "render menus as 4:3" hack + } } if (h) { *h *= yScale; + if(r_scaleMenusTo43.GetBool()) + { + *h *= fixScaleForMenu.y; // DG: for "render menus as 4:3" hack + } } } @@ -716,8 +738,22 @@ void idDeviceContext::DrawCursor(float *x, float *y, float size) } renderSystem->SetColor(colorWhite); - AdjustCoords(x, y, &size, &size); - DrawStretchPic(*x, *y, size, size, 0, 0, 1, 1, cursorImages[cursor]); + if(r_scaleMenusTo43.GetBool()) + { + // DG: I use this instead of plain AdjustCursorCoords and the following lines + // to scale menus and other fullscreen GUIs to 4:3 aspect ratio + AdjustCursorCoords(x, y, &size, &size); + float sizeW = size * fixScaleForMenu.x; + float sizeH = size * fixScaleForMenu.y; + float fixedX = *x * fixScaleForMenu.x + fixOffsetForMenu.x; + float fixedY = *y * fixScaleForMenu.y + fixOffsetForMenu.y; + DrawStretchPic(fixedX, fixedY, sizeW, sizeH, 0, 0, 1, 1, cursorImages[cursor]); + } + else + { + AdjustCoords(x, y, &size, &size); + DrawStretchPic(*x, *y, size, size, 0, 0, 1, 1, cursorImages[cursor]); + } } /* ======================================================================================================================= @@ -1213,3 +1249,52 @@ char *idRectangle::String(void) const return s; } + +// DG: this is used for the "make sure menus are rendered as 4:3" hack +void idDeviceContext::SetMenuScaleFix(bool enable) { + if(enable) { + float w = renderSystem->GetScreenWidth(); + float h = renderSystem->GetScreenHeight(); + float aspectRatio = w/h; + static const float virtualAspectRatio = float(VIRTUAL_WIDTH)/float(VIRTUAL_HEIGHT); // 4:3 + if(aspectRatio > 1.4f) { + // widescreen (4:3 is 1.333 3:2 is 1.5, 16:10 is 1.6, 16:9 is 1.7778) + // => we need to scale and offset X + // All the coordinates here assume 640x480 (VIRTUAL_WIDTH x VIRTUAL_HEIGHT) + // screensize, so to fit a 4:3 menu into 640x480 stretched to a widescreen, + // we need do decrease the width to something smaller than 640 and center + // the result with an offset + float scaleX = virtualAspectRatio/aspectRatio; + float offsetX = (1.0f-scaleX)*(VIRTUAL_WIDTH*0.5f); // (640 - scale*640)/2 + fixScaleForMenu.Set(scaleX, 1); + fixOffsetForMenu.Set(offsetX, 0); + } else if(aspectRatio < 1.24f) { + // portrait-mode, "thinner" than 5:4 (which is 1.25) + // => we need to scale and offset Y + // it's analogue to the other case, but inverted and with height and Y + float scaleY = aspectRatio/virtualAspectRatio; + float offsetY = (1.0f - scaleY)*(VIRTUAL_HEIGHT*0.5f); // (480 - scale*480)/2 + fixScaleForMenu.Set(1, scaleY); + fixOffsetForMenu.Set(0, offsetY); + } + } else { + fixScaleForMenu.Set(1, 1); + fixOffsetForMenu.Set(0, 0); + } +} + +// DG: same as AdjustCoords, but ignore fixupMenus because for the cursor that must be handled seperately +void idDeviceContext::AdjustCursorCoords(float *x, float *y, float *w, float *h) { + if (x) { + *x *= xScale; + } + if (y) { + *y *= yScale; + } + if (w) { + *w *= xScale; + } + if (h) { + *h *= yScale; + } +} \ No newline at end of file diff --git a/Q3E/src/main/jni/doom3/neo/ui/DeviceContext.h b/Q3E/src/main/jni/doom3/neo/ui/DeviceContext.h index 6a1c1c2a2..9557352f5 100644 --- a/Q3E/src/main/jni/doom3/neo/ui/DeviceContext.h +++ b/Q3E/src/main/jni/doom3/neo/ui/DeviceContext.h @@ -33,6 +33,7 @@ If you have questions concerning this license or the applicable additional terms // #include "Rectangle.h" +extern idCVar r_scaleMenusTo43; const int VIRTUAL_WIDTH = 640; const int VIRTUAL_HEIGHT = 480; @@ -105,6 +106,11 @@ class idDeviceContext void DrawEditCursor(float x, float y, float scale); + void SetMenuScaleFix(bool enable); + bool IsMenuScaleFixActive() const { + return r_scaleMenusTo43.GetBool() && (fixOffsetForMenu.x != 0.0f || fixOffsetForMenu.y != 0.0f); + } + enum { CURSOR_ARROW, CURSOR_HAND, @@ -143,6 +149,7 @@ class idDeviceContext void PaintChar(float x,float y,float width,float height,float scale,float s,float t,float s2,float t2,const idMaterial *hShader); void SetFontByScale(float scale); void Clear(void); + void AdjustCursorCoords(float *x, float *y, float *w, float *h); // DG: added for "render menus as 4:3" hack const idMaterial *cursorImages[CURSOR_COUNT]; const idMaterial *scrollBarImages[SCROLLBAR_COUNT]; @@ -172,6 +179,10 @@ class idDeviceContext bool initialized; bool mbcs; + + // DG: this is used for the "make sure menus are rendered as 4:3" hack + idVec2 fixScaleForMenu; + idVec2 fixOffsetForMenu; }; #endif /* !__DEVICECONTEXT_H__ */ diff --git a/Q3E/src/main/jni/doom3/neo/ui/RenderWindow.cpp b/Q3E/src/main/jni/doom3/neo/ui/RenderWindow.cpp index 875dee60e..1ef8119a5 100644 --- a/Q3E/src/main/jni/doom3/neo/ui/RenderWindow.cpp +++ b/Q3E/src/main/jni/doom3/neo/ui/RenderWindow.cpp @@ -189,10 +189,31 @@ void idRenderWindow::Draw(int time, float x, float y) refdef.shaderParms[2] = 1; refdef.shaderParms[3] = 1; - refdef.x = drawRect.x; - refdef.y = drawRect.y; - refdef.width = drawRect.w; - refdef.height = drawRect.h; + if(r_scaleMenusTo43.GetBool()) + { + // DG: for scaling menus to 4:3 (like that spinning mars globe in the main menu) + float x = drawRect.x; + float y = drawRect.y; + float w = drawRect.w; + float h = drawRect.h; + if(dc->IsMenuScaleFixActive()) { + dc->AdjustCoords(&x, &y, &w, &h); + } + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + // DG end + } + else + { + refdef.x = drawRect.x; + refdef.y = drawRect.y; + refdef.width = drawRect.w; + refdef.height = drawRect.h; + } + refdef.fov_x = 90; refdef.fov_y = 2 * atan((float)drawRect.h / drawRect.w) * idMath::M_RAD2DEG; diff --git a/Q3E/src/main/jni/doom3/neo/ui/UserInterface.cpp b/Q3E/src/main/jni/doom3/neo/ui/UserInterface.cpp index 0dc7dd69f..c0f9c9e7d 100644 --- a/Q3E/src/main/jni/doom3/neo/ui/UserInterface.cpp +++ b/Q3E/src/main/jni/doom3/neo/ui/UserInterface.cpp @@ -410,8 +410,40 @@ const char *idUserInterfaceLocal::HandleEvent(const sysEvent_t *event, int _time } if (event->evType == SE_MOUSE) { - cursorX += event->evValue; - cursorY += event->evValue2; + extern idCVar r_scaleMenusTo43; // DG: for the "scale menus to 4:3" hack + if(r_scaleMenusTo43.GetBool() && (!desktop || (desktop->GetFlags() & WIN_MENUGUI))) { + // DG: this is a fullscreen GUI, scale the mousedelta added to cursorX/Y + // by 640/w, because the GUI pretends that everything is 640x480 + // even if the actual resolution is higher => mouse moved too fast + float w = renderSystem->GetScreenWidth(); + float h = renderSystem->GetScreenHeight(); + if( w <= 0.0f || h <= 0.0f ) { + w = VIRTUAL_WIDTH; + h = VIRTUAL_HEIGHT; + } + // in case we're scaling menus to 4:3, we need to take that into account + // when scaling the mouse events. + // no, we can't just call uiManagerLocal.dc.GetFixScaleForMenu() or sth like that, + // because when we're here dc.SetMenuScaleFix(true) is not active and it'd just return (1, 1)! + float aspectRatio = w/h; + static const float virtualAspectRatio = float(VIRTUAL_WIDTH)/float(VIRTUAL_HEIGHT); // 4:3 + if(aspectRatio > 1.4f) { + // widescreen (4:3 is 1.333 3:2 is 1.5, 16:10 is 1.6, 16:9 is 1.7778) + // => we need to modify cursorX scaling, by modifying w + w *= virtualAspectRatio/aspectRatio; + } else if(aspectRatio < 1.24f) { + // portrait-mode, "thinner" than 5:4 (which is 1.25) + // => we need to scale cursorY via h + h *= aspectRatio/virtualAspectRatio; + } + cursorX += event->evValue * (float(VIRTUAL_WIDTH)/w); + cursorY += event->evValue2 * (float(VIRTUAL_HEIGHT)/h); + } + else + { + cursorX += event->evValue; + cursorY += event->evValue2; + } if (cursorX < 0) { cursorX = 0; diff --git a/Q3E/src/main/jni/doom3/neo/ui/Window.cpp b/Q3E/src/main/jni/doom3/neo/ui/Window.cpp index 7a2414950..cd1158cbe 100644 --- a/Q3E/src/main/jni/doom3/neo/ui/Window.cpp +++ b/Q3E/src/main/jni/doom3/neo/ui/Window.cpp @@ -60,6 +60,7 @@ bool idWindow::registerIsTemporary[MAX_EXPRESSION_REGISTERS]; // statics to ass idCVar idWindow::gui_debug("gui_debug", "0", CVAR_GUI | CVAR_BOOL, ""); idCVar idWindow::gui_edit("gui_edit", "0", CVAR_GUI | CVAR_BOOL, ""); +extern idCVar r_scaleMenusTo43; #ifdef _RAVEN //k: for main menu gui idCVar net_menulanserver("net_menuLANServer", "0", CVAR_SYSTEM | CVAR_ARCHIVE, "menu cvar for config of lan servers"); @@ -1529,6 +1530,19 @@ void idWindow::Redraw(float x, float y) return; } + // DG: allow scaling menus to 4:3 + bool fixupFor43 = false; + if ( flags & WIN_DESKTOP ) { + // only scale desktop windows (will automatically scale its sub-windows) + // that EITHER have the scaleto43 flag set OR are fullscreen menus and r_scaleMenusTo43 is 1 + if( /*(flags & WIN_SCALETO43) ||*/ + ((flags & WIN_MENUGUI) && r_scaleMenusTo43.GetBool()) ) + { + fixupFor43 = true; + dc->SetMenuScaleFix(true); + } + } + if (flags & WIN_SHOWTIME) { dc->DrawText(va(" %0.1f seconds\n%s", (float)(time - timeLine) / 1000, gui->State().GetString("name")), 0.35f, 0, dc->colorWhite, idRectangle(100, 0, 80, 80), false); } @@ -1541,6 +1555,9 @@ void idWindow::Redraw(float x, float y) } if (!visible) { + if (fixupFor43) { // DG: gotta reset that before returning this function + dc->SetMenuScaleFix(false); + } return; } @@ -1616,6 +1633,9 @@ void idWindow::Redraw(float x, float y) dc->EnableClipping(true); } + if (fixupFor43) { // DG: gotta reset that before returning this function + dc->SetMenuScaleFix(false); + } drawRect.Offset(-x, -y); clientRect.Offset(-x, -y); textRect.Offset(-x, -y); diff --git a/README.md b/README.md index 864a32f82..b0fd724bd 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ ## idTech4A++ (Harmattan Edition) #### DIII4A++, com.n0n3m4.diii4a, DOOM III/Quake 4/Prey(2006) for Android, 毁灭战士3/雷神之锤4/掠食(2006)安卓移植版 **Latest version:** -1.1.0harmattan36(natasha) +1.1.0harmattan37(natasha) **Last update release:** -2023-12-31 +2024-01-06 **Arch:** arm64 armv7-a **Platform:** @@ -15,7 +15,7 @@ GPLv3 ### Feature * multi-threading renderer * png/dds texture image -* jpeg/png/bmp format of screenshot +* jpeg/png/bmp/dds format of screenshot * obj format static model * dae format static model * pure soft shadow with shadow-mapping @@ -41,18 +41,10 @@ Tag with `-free` only for F-Droid update. ---------------------------------------------------------------------------------- ### Update -* Fixed prelight shadow's shadow mapping. -* Fixed EFX Reverb in Quake4. -* Add translucent stencil shadow support in stencil shadow(bool cvar `harm_r_translucentStencilShadow`(default 0); float cvar `harm_r_stencilShadowAlpha` for setting transparency). -* Add float cvar `harm_ui_subtitlesTextScale` control subtitles's text scale in Prey. -* Support cvar `r_brightness`. -* Fixed weapon projectile's scorches decals rendering in Prey(2006). -* Data directory chooser support Android SAF. -* New default on-screen buttons layout. -* Add `Stupid Angry Bot`(a7x) mod of DOOM3 support(need DOOM3: RoE game data), game data directory named `sabot`. More view in [SABot(a7x)](https://www.moddb.com/downloads/sabot-alpha-7x). -* Add `Overthinked DooM^3` mod of DOOM3 support, game data directory named `overthinked`. More view in [Overthinked DooM^3](https://www.moddb.com/mods/overthinked-doom3). -* Add `Fragging Free` mod of DOOM3 support(need DOOM3: RoE game data), game data directory named `fraggingfree`. More view in [Fragging Free](https://www.moddb.com/mods/fragging-free). -* Add `HeXen:Edge of Chaos` mod of DOOM3 support, game data directory named `hexeneoc`. More view in [Overthinked DooM^3](https://www.moddb.com/mods/hexen-edge-of-chaos). +* Fixed on-screen buttons initial keycodes. +* On-screen slider button can setup clickable. +* Add dds screenshot support. +* Add cvar `r_scaleMenusTo43` for 4:3 menu. ---------------------------------------------------------------------------------- diff --git a/README.zh.md b/README.zh.md index 6d134de60..f3cce1a08 100644 --- a/README.zh.md +++ b/README.zh.md @@ -1,9 +1,9 @@ ## idTech4A++ (Harmattan Edition) #### DIII4A++, com.n0n3m4.diii4a, DOOM III/Quake 4/Prey(2006) for Android, 毁灭战士3/雷神之锤4/掠食(2006)安卓移植版 **最新版本:** -1.1.0harmattan36(natasha) +1.1.0harmattan37(natasha) **最新更新日期:** -2023-12-31 +2024-01-06 **架构支持:** arm64 armv7-a **平台:** @@ -15,7 +15,7 @@ GPLv3 ### 支持 * 多线程渲染 * png/dds纹理图片加载 -* jpeg/png/bmp格式截屏 +* jpeg/png/bmp/dds格式截屏 * obj格式静态模型 * dae格式静态模型 * 纯阴影图映射软阴影 @@ -41,18 +41,10 @@ height="80">](https://f-droid.org/packages/com.karin.idTech4Amm/) ---------------------------------------------------------------------------------- ### 更新 -* 修复预烘培阴影图软阴影渲染. -* 雷神之锤4修复EFX混响. -* 添加半透明模板阴影支持(bool型cvar `harm_r_translucentStencilShadow`(默认 0); 浮点型cvar `harm_r_stencilShadowAlpha`设置透明度). -* 掠食(2006)添加浮点型cvar `harm_ui_subtitlesTextScale`控制字幕字体大小. -* 支持cvar `r_brightness`. -* 掠食(2006)修复武器发射爆炸贴花渲染Z-Fighting. -* 数据文件目录选择器支持安卓SAF框架. -* 新的默认屏幕按键布局. -* 添加毁灭战士3 mod `Stupid Angry Bot`(a7x)(需要邪恶复苏数据包), 游戏数据文件夹名为`sabot`. 详情 [SABot(a7x)](https://www.moddb.com/downloads/sabot-alpha-7x). -* 添加毁灭战士3 mod `Overthinked DooM^3`, 游戏数据文件夹名为`overthinked`. 详情 [Overthinked DooM^3](https://www.moddb.com/mods/overthinked-doom3). -* 添加毁灭战士3 mod `Fragging Free`(需要邪恶复苏数据包), 游戏数据文件夹名为`fraggingfree`. 详情 [Fragging Free](https://www.moddb.com/mods/fragging-free). -* 添加毁灭战士3 mod `HeXen:Edge of Chaos`, 游戏数据文件夹名为`hexeneoc`. 详情 [Overthinked DooM^3](https://www.moddb.com/mods/hexen-edge-of-chaos). +* 修复屏幕按键初始键码配置. +* 滑动按键支持设置为点击触发. +* 新增dds格式屏幕截图. +* 新增cvar `r_scaleMenusTo43`启用4:3比例菜单. ---------------------------------------------------------------------------------- diff --git a/fastlane/metadata/android/en-US/changelogs/11036.txt b/fastlane/metadata/android/en-US/changelogs/11036.txt index c32711eb0..f3af3f103 100644 --- a/fastlane/metadata/android/en-US/changelogs/11036.txt +++ b/fastlane/metadata/android/en-US/changelogs/11036.txt @@ -6,7 +6,4 @@ * Fixed weapon projectile's scorches decals rendering in Prey(2006). * Data directory chooser support Android SAF. * New default on-screen buttons layout. -* Add `Stupid Angry Bot`(a7x) mod of DOOM3 support. -* Add `Overthinked DooM^3` mod of DOOM3 support. -* Add `Fragging Free` mod of DOOM3 support. -* Add `HeXen:Edge of Chaos` mod of DOOM3 support. \ No newline at end of file +* Add `Stupid Angry Bot`(a7x), `Overthinked DooM^3`, `Fragging Free`, `HeXen:Edge of Chaos` mod of DOOM3 support. \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/11037.txt b/fastlane/metadata/android/en-US/changelogs/11037.txt new file mode 100644 index 000000000..f1b5b4e9e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/11037.txt @@ -0,0 +1,4 @@ +* Fixed on-screen buttons initial keycodes. +* On-screen slider button can setup clickable. +* Add dds screenshot support. +* Add cvar `r_scaleMenusTo43` for 4:3 menu. \ No newline at end of file diff --git a/fastlane/metadata/android/zh-CN/changelogs/11036.txt b/fastlane/metadata/android/zh-CN/changelogs/11036.txt index 11824030d..f4624cc20 100644 --- a/fastlane/metadata/android/zh-CN/changelogs/11036.txt +++ b/fastlane/metadata/android/zh-CN/changelogs/11036.txt @@ -6,7 +6,4 @@ * 掠食(2006)修复武器发射爆炸贴花渲染Z-Fighting. * 数据文件目录选择器支持安卓SAF框架. * 新的默认屏幕按键布局. -* 添加毁灭战士3 mod `Stupid Angry Bot`(a7x). -* 添加毁灭战士3 mod `Overthinked DooM^3`. -* 添加毁灭战士3 mod `Fragging Free`. -* 添加毁灭战士3 mod `HeXen:Edge of Chaos`. \ No newline at end of file +* 添加毁灭战士3 mod `Stupid Angry Bot`(a7x), `Overthinked DooM^3`, `Fragging Free`, `HeXen:Edge of Chaos`. \ No newline at end of file diff --git a/fastlane/metadata/android/zh-CN/changelogs/11037.txt b/fastlane/metadata/android/zh-CN/changelogs/11037.txt new file mode 100644 index 000000000..802aff659 --- /dev/null +++ b/fastlane/metadata/android/zh-CN/changelogs/11037.txt @@ -0,0 +1,4 @@ +* 修复屏幕按键初始键码配置. +* 滑动按键支持设置为点击触发. +* 新增dds格式屏幕截图. +* 新增cvar `r_scaleMenusTo43`启用4:3比例菜单. \ No newline at end of file diff --git a/idTech4Amm/build.gradle b/idTech4Amm/build.gradle index b36a709db..1fc2121da 100644 --- a/idTech4Amm/build.gradle +++ b/idTech4Amm/build.gradle @@ -10,8 +10,8 @@ android { applicationId "com.karin.idTech4Amm" minSdkVersion project.properties.minSdkVersion.toInteger() targetSdkVersion project.properties.targetSdkVersion.toInteger() - versionCode 11036 - versionName '1.1.0harmattan36' + versionCode 11037 + versionName '1.1.0harmattan37' versionNameSuffix 'natasha' buildConfigField "long", "BUILD_TIMESTAMP", System.currentTimeMillis() + "L" buildConfigField "int", "BUILD_SDK_VERSION", 30 + "" diff --git a/idTech4Amm/src/main/AndroidManifest.xml b/idTech4Amm/src/main/AndroidManifest.xml index d16b551de..04d8c8331 100644 --- a/idTech4Amm/src/main/AndroidManifest.xml +++ b/idTech4Amm/src/main/AndroidManifest.xml @@ -1,8 +1,8 @@ diff --git a/idTech4Amm/src/main/assets/source/DIII4A.source.tgz b/idTech4Amm/src/main/assets/source/DIII4A.source.tgz index 506648ff0..a1dd2eb84 100644 Binary files a/idTech4Amm/src/main/assets/source/DIII4A.source.tgz and b/idTech4Amm/src/main/assets/source/DIII4A.source.tgz differ diff --git a/idTech4Amm/src/main/java/com/karin/idTech4Amm/lib/KCVarSystem.java b/idTech4Amm/src/main/java/com/karin/idTech4Amm/lib/KCVarSystem.java index 69279b447..d92c60a2b 100644 --- a/idTech4Amm/src/main/java/com/karin/idTech4Amm/lib/KCVarSystem.java +++ b/idTech4Amm/src/main/java/com/karin/idTech4Amm/lib/KCVarSystem.java @@ -36,7 +36,8 @@ public static Map CVars() "0", "TGA (default)", "1", "BMP", "2", "PNG", - "3", "JPG" + "3", "JPG", + "4", "DDS" ), KCVar.CreateCVar("r_screenshotJpgQuality", "integer", "75", "Screenshot quality for JPG images (0-100)", KCVar.FLAG_POSITIVE), KCVar.CreateCVar("r_screenshotPngCompression", "integer", "3", "Compression level when using PNG screenshots (0-9)", KCVar.FLAG_POSITIVE), @@ -61,7 +62,8 @@ public static Map CVars() "0", "disable", "1", "loading in engine initialization, and saving in engine shutdown", "2", "loading in engine initialization, and saving in every e executing" - ) + ), + KCVar.CreateCVar("r_scaleMenusTo43", "bool", "0", "Scale menus, fullscreen videos and PDA to 4:3 aspect ratio", 0) ); KCVar.Group GAME_CVARS = new KCVar.Group("DOOM3", false) .AddCVar( diff --git a/idTech4Amm/src/main/java/com/karin/idTech4Amm/misc/TextHelper.java b/idTech4Amm/src/main/java/com/karin/idTech4Amm/misc/TextHelper.java index f53a4e577..3fbadec69 100644 --- a/idTech4Amm/src/main/java/com/karin/idTech4Amm/misc/TextHelper.java +++ b/idTech4Amm/src/main/java/com/karin/idTech4Amm/misc/TextHelper.java @@ -314,6 +314,21 @@ public static CharSequence GetChangesText() final ChangeLog[] CHANGES = { ChangeLog.Create(Constants.CONST_RELEASE, Constants.CONST_UPDATE_RELEASE, Constants.CONST_CHANGES), + ChangeLog.Create("2023-12-31", 36, + "Fixed prelight shadow's shadow mapping.", + "Fixed EFX Reverb in Quake4.", + "Add translucent stencil shadow support in stencil shadow(bool cvar `harm_r_translucentStencilShadow`(default 0); float cvar `harm_r_stencilShadowAlpha` for setting transparency).", + "Add float cvar `harm_ui_subtitlesTextScale` control subtitles's text scale in Prey.", + "Support cvar `r_brightness`.", + "Fixed weapon projectile's scorches decals rendering in Prey(2006).", + "Data directory chooser support Android SAF.", + "New default on-screen buttons layout.", + "Add `Stupid Angry Bot`(a7x) mod of DOOM3 support(need DOOM3: RoE game data), game data directory named `sabot`. More view in`" + TextHelper.GenLinkText("https://www.moddb.com/downloads/sabot-alpha-7x", "SABot(a7x)") + "`.", + "Add `Overthinked DooM^3` mod of DOOM3 support, game data directory named `overthinked`. More view in`" + TextHelper.GenLinkText("https://www.moddb.com/mods/overthinked-doom3", "Overthinked DooM^3") + "`.", + "Add `Fragging Free` mod of DOOM3 support(need DOOM3: RoE game data), game data directory named `fraggingfree`. More view in`" + TextHelper.GenLinkText("https://www.moddb.com/mods/fragging-free", "Fragging Free") + "`.", + "Add `HeXen:Edge of Chaos` mod of DOOM3 support, game data directory named `hexeneoc`. More view in`" + TextHelper.GenLinkText("https://www.moddb.com/mods/hexen-edge-of-chaos", "HexenEOC") + "`." + ), + ChangeLog.Create("2023-10-29", 35, "Optimize soft shadow with shadow mapping. Add shadow map with depth texture in OpenGLES2.0.", "Add OpenAL(soft) and EFX Reverb support.", diff --git a/idTech4Amm/src/main/java/com/karin/idTech4Amm/sys/Constants.java b/idTech4Amm/src/main/java/com/karin/idTech4Amm/sys/Constants.java index a56377017..82bcfd6e7 100644 --- a/idTech4Amm/src/main/java/com/karin/idTech4Amm/sys/Constants.java +++ b/idTech4Amm/src/main/java/com/karin/idTech4Amm/sys/Constants.java @@ -12,8 +12,8 @@ */ public final class Constants { - public static final int CONST_UPDATE_RELEASE = 36; - public static final String CONST_RELEASE = "2023-12-31"; + public static final int CONST_UPDATE_RELEASE = 37; + public static final String CONST_RELEASE = "2024-01-06"; public static final String CONST_EMAIL = "beyondk2000@gmail.com"; public static final String CONST_DEV = "Karin"; public static final String CONST_CODE = "Harmattan"; @@ -28,18 +28,10 @@ public final class Constants public static final String CONST_CHECK_FOR_UPDATE_URL = "https://raw.githubusercontent.com/glKarin/com.n0n3m4.diii4a/master/CHECK_FOR_UPDATE.json"; public static final String CONST_LICENSE_URL = "https://raw.githubusercontent.com/glKarin/com.n0n3m4.diii4a/master/LICENSE"; public static final String[] CONST_CHANGES = { - "Fixed prelight shadow's shadow mapping.", - "Fixed EFX Reverb in Quake4.", - "Add translucent stencil shadow support in stencil shadow(bool cvar `harm_r_translucentStencilShadow`(default 0); float cvar `harm_r_stencilShadowAlpha` for setting transparency).", - "Add float cvar `harm_ui_subtitlesTextScale` control subtitles's text scale in Prey.", - "Support cvar `r_brightness`.", - "Fixed weapon projectile's scorches decals rendering in Prey(2006).", - "Data directory chooser support Android SAF.", - "New default on-screen buttons layout.", - "Add `Stupid Angry Bot`(a7x) mod of DOOM3 support(need DOOM3: RoE game data), game data directory named `sabot`. More view in`" + TextHelper.GenLinkText("https://www.moddb.com/downloads/sabot-alpha-7x", "SABot(a7x)") + "`.", - "Add `Overthinked DooM^3` mod of DOOM3 support, game data directory named `overthinked`. More view in`" + TextHelper.GenLinkText("https://www.moddb.com/mods/overthinked-doom3", "Overthinked DooM^3") + "`.", - "Add `Fragging Free` mod of DOOM3 support(need DOOM3: RoE game data), game data directory named `fraggingfree`. More view in`" + TextHelper.GenLinkText("https://www.moddb.com/mods/fragging-free", "Fragging Free") + "`.", - "Add `HeXen:Edge of Chaos` mod of DOOM3 support, game data directory named `hexeneoc`. More view in`" + TextHelper.GenLinkText("https://www.moddb.com/mods/hexen-edge-of-chaos", "HexenEOC") + "`.", + "Fixed on-screen buttons initial keycodes.", + "On-screen slider button can setup clickable.", + "Add dds screenshot support.", + "Add cvar `r_scaleMenusTo43` for 4:3 menu.", }; public static long GetBuildTimestamp() diff --git a/idTech4Amm/src/main/java/com/n0n3m4/DIII4A/GameLauncher.java b/idTech4Amm/src/main/java/com/n0n3m4/DIII4A/GameLauncher.java index 518914a94..f9be1963a 100644 --- a/idTech4Amm/src/main/java/com/n0n3m4/DIII4A/GameLauncher.java +++ b/idTech4Amm/src/main/java/com/n0n3m4/DIII4A/GameLauncher.java @@ -515,7 +515,7 @@ private void InitUIDefaultLayout(Q3EInterface q3ei) public void InitQ3E() { - Q3EKeyCodes.InitD3Keycodes(); + // Q3EKeyCodes.InitD3Keycodes(); Q3EInterface q3ei = new Q3EInterface(); q3ei.InitD3(); diff --git a/idTech4Amm/src/main/res/values-ru/arrays.xml b/idTech4Amm/src/main/res/values-ru/arrays.xml index 7cd3764c2..4d1534d10 100644 --- a/idTech4Amm/src/main/res/values-ru/arrays.xml +++ b/idTech4Amm/src/main/res/values-ru/arrays.xml @@ -21,6 +21,8 @@ Лево-право Вниз-право + Лево-право(click) + Вниз-право(click) diff --git a/idTech4Amm/src/main/res/values-zh/arrays.xml b/idTech4Amm/src/main/res/values-zh/arrays.xml index 7c09a6406..d424265e0 100644 --- a/idTech4Amm/src/main/res/values-zh/arrays.xml +++ b/idTech4Amm/src/main/res/values-zh/arrays.xml @@ -21,6 +21,8 @@ 左-右方向 下-右方向 + 左-右方向(点击) + 下-右方向(点击) diff --git a/idTech4Amm/src/main/res/values/arrays.xml b/idTech4Amm/src/main/res/values/arrays.xml index 94f6a65d2..850ea0115 100644 --- a/idTech4Amm/src/main/res/values/arrays.xml +++ b/idTech4Amm/src/main/res/values/arrays.xml @@ -39,11 +39,15 @@ Left-right Down-right + Left-right(click) + Down-right(click) 0 1 + 2 + 3