diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..bdb0cab
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,17 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Custom for Visual Studio
+*.cs diff=csharp
+
+# Standard to msysgit
+*.doc diff=astextplain
+*.DOC diff=astextplain
+*.docx diff=astextplain
+*.DOCX diff=astextplain
+*.dot diff=astextplain
+*.DOT diff=astextplain
+*.pdf diff=astextplain
+*.PDF diff=astextplain
+*.rtf diff=astextplain
+*.RTF diff=astextplain
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6fa5f59
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,61 @@
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# =========================
+# Operating System Files
+# =========================
+
+# OSX
+# =========================
+
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Thumbnails
+._*
+
+# Files that might appear on external disk
+.Spotlight-V100
+.Trashes
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+
+# build files
+*.o
+*.d
+
+#binaries
+*.so
+
+#files created by build
+*.apk
+*.ap_
+*.class
+*.log
+bin/*
+gen/*
+.idea/*
+.gradle/*
diff --git a/QVR.iml b/QVR.iml
new file mode 100644
index 0000000..225b52a
--- /dev/null
+++ b/QVR.iml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/app.iml b/app/app.iml
new file mode 100644
index 0000000..540250c
--- /dev/null
+++ b/app/app.iml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ generateDebugAndroidTestSources
+ generateDebugSources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..09a43aa
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'com.android.application'
+
+android {
+ compileSdkVersion 16
+ buildToolsVersion '19.1.0'
+ defaultConfig {
+ applicationId "com.drbeef.qvr"
+ minSdkVersion 16
+ targetSdkVersion 19
+ versionCode 1
+ versionName '1.0.0'
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ productFlavors {
+ }
+ }
+
+dependencies {
+ compile fileTree(include: ['*.jar'], dir: 'libs')
+}
diff --git a/app/jni/Android.mk b/app/jni/Android.mk
new file mode 100644
index 0000000..9f1c01b
--- /dev/null
+++ b/app/jni/Android.mk
@@ -0,0 +1,117 @@
+
+LOCAL_PATH:= $(call my-dir)
+
+#--------------------------------------------------------
+# libquakecardboard.so
+#--------------------------------------------------------
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := qvr
+LOCAL_CFLAGS := -std=c99
+LOCAL_SRC_FILES := QVR.c
+LOCAL_LDLIBS := -llog -landroid -lGLESv2 -lEGL # include default libraries
+
+# CD objects
+SRC_NOCD=cd_null.c
+
+SRC_SND_COMMON=snd_main.c snd_mem.c snd_mix.c snd_ogg.c snd_wav.c snd_modplug.c
+
+
+
+###### Common objects and flags #####
+
+# Common objects
+SRC_COMMON= \
+ bih.c \
+ cap_avi.c \
+ cap_ogg.c \
+ cd_shared.c \
+ crypto.c \
+ cl_collision.c \
+ cl_demo.c \
+ cl_dyntexture.c \
+ cl_input.c \
+ cl_main.c \
+ cl_parse.c \
+ cl_particles.c \
+ cl_screen.c \
+ cl_video.c \
+ clvm_cmds.c \
+ cmd.c \
+ collision.c \
+ common.c \
+ console.c \
+ csprogs.c \
+ curves.c \
+ cvar.c \
+ dpsoftrast.c \
+ dpvsimpledecode.c \
+ filematch.c \
+ fractalnoise.c \
+ fs.c \
+ ft2.c \
+ utf8lib.c \
+ gl_backend.c \
+ gl_draw.c \
+ gl_rmain.c \
+ gl_rsurf.c \
+ gl_textures.c \
+ hmac.c \
+ host.c \
+ host_cmd.c \
+ image.c \
+ image_png.c \
+ jpeg.c \
+ keys.c \
+ lhnet.c \
+ libcurl.c \
+ mathlib.c \
+ matrixlib.c \
+ mdfour.c \
+ menu.c \
+ meshqueue.c \
+ mod_skeletal_animatevertices_sse.c \
+ mod_skeletal_animatevertices_generic.c \
+ model_alias.c \
+ model_brush.c \
+ model_shared.c \
+ model_sprite.c \
+ mvm_cmds.c \
+ netconn.c \
+ palette.c \
+ polygon.c \
+ portals.c \
+ protocol.c \
+ prvm_cmds.c \
+ prvm_edict.c \
+ prvm_exec.c \
+ r_explosion.c \
+ r_lerpanim.c \
+ r_lightning.c \
+ r_modules.c \
+ r_shadow.c \
+ r_sky.c \
+ r_sprites.c \
+ sbar.c \
+ snprintf.c \
+ sv_demo.c \
+ sv_main.c \
+ sv_move.c \
+ sv_phys.c \
+ sv_user.c \
+ svbsp.c \
+ svvm_cmds.c \
+ sys_shared.c \
+ vid_shared.c \
+ view.c \
+ wad.c \
+ world.c \
+ zone.c
+
+
+SRC_ANDROID= builddate.c sys_linux.c vid_android.c thread_pthread.c snd_android.c $(SRC_SND_COMMON) $(SRC_NOCD) $(SRC_COMMON)
+
+LOCAL_SRC_FILES += $(SRC_ANDROID)
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/app/jni/Application.mk b/app/jni/Application.mk
new file mode 100644
index 0000000..129f439
--- /dev/null
+++ b/app/jni/Application.mk
@@ -0,0 +1,22 @@
+# Common build settings for all VR apps
+
+# This needs to be defined to get the right header directories for egl / etc
+APP_PLATFORM := android-16
+
+APP_ABI := armeabi-v7a
+
+# Statically link the GNU STL. This may not be safe for multi-so libraries but
+# we don't know of any problems yet.
+APP_STL := gnustl_static
+
+# Make sure every shared lib includes a .note.gnu.build-id header, for crash reporting
+APP_LDFLAGS := -Wl,--build-id
+
+# Explicitly use GCC 4.8 as our toolchain. This is the 32-bit default as of
+# r10d but versions as far back as r9d have 4.8. The previous default, 4.6, is
+# deprecated as of r10c.
+NDK_TOOLCHAIN_VERSION := 4.8
+
+# Define the directories for $(import-module, ...) to look in
+ROOT_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+NDK_MODULE_PATH :=
diff --git a/app/jni/COPYING b/app/jni/COPYING
new file mode 100644
index 0000000..d60c31a
--- /dev/null
+++ b/app/jni/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/app/jni/QVR.c b/app/jni/QVR.c
new file mode 100644
index 0000000..29557d0
--- /dev/null
+++ b/app/jni/QVR.c
@@ -0,0 +1,449 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include // for native window JNI
+#include
+
+#include
+#include
+#include
+#include
+
+//All the functionality we link to in the DarkPlaces Engine implementation
+extern void QC_BeginFrame();
+extern void QC_DrawFrame(int eye, int x, int y);
+extern void QC_EndFrame();
+extern void QC_GetAudio();
+extern void QC_KeyEvent(int state,int key,int character);
+extern void QC_MoveEvent(float yaw, float pitch, float roll);
+extern void QC_SetCallbacks(void *init_audio, void *write_audio);
+extern void QC_SetResolution(int width, int height);
+extern void QC_Analog(int enable,float x,float y);
+extern void QC_MotionEvent(float delta, float dx, float dy);
+extern int main (int argc, char **argv);
+
+extern qboolean vrMode;
+extern int bigScreen;
+extern int gameAssetsDownloadStatus;
+
+extern cvar_t cl_autocentreoffset;
+extern cvar_t v_eyebufferresolution;
+
+static JavaVM *jVM;
+static jobject audioBuffer=0;
+static jobject audioCallbackObj=0;
+
+jmethodID android_initAudio;
+jmethodID android_writeAudio;
+jmethodID android_pauseAudio;
+jmethodID android_resumeAudio;
+jmethodID android_terminateAudio;
+
+static jobject quakeCallbackObj=0;
+jmethodID android_BigScreenMode;
+jmethodID android_SwitchVRMode;
+jmethodID android_SwitchStereoMode;
+jmethodID android_Exit;
+
+void jni_initAudio(void *buffer, int size)
+{
+ JNIEnv *env;
+ jobject tmp;
+ (*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4);
+ tmp = (*env)->NewDirectByteBuffer(env, buffer, size);
+ audioBuffer = (jobject)(*env)->NewGlobalRef(env, tmp);
+ return (*env)->CallVoidMethod(env, audioCallbackObj, android_initAudio, size);
+}
+
+void jni_writeAudio(int offset, int length)
+{
+ if (audioBuffer==0) return;
+ JNIEnv *env;
+ if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0)
+ {
+ (*jVM)->AttachCurrentThread(jVM,&env, NULL);
+ }
+ (*env)->CallVoidMethod(env, audioCallbackObj, android_writeAudio, audioBuffer, offset, length);
+}
+
+void jni_pauseAudio()
+{
+ if (audioBuffer==0) return;
+ JNIEnv *env;
+ if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0)
+ {
+ (*jVM)->AttachCurrentThread(jVM,&env, NULL);
+ }
+ (*env)->CallVoidMethod(env, audioCallbackObj, android_pauseAudio);
+}
+
+void jni_resumeAudio()
+{
+ if (audioBuffer==0) return;
+ JNIEnv *env;
+ if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0)
+ {
+ (*jVM)->AttachCurrentThread(jVM,&env, NULL);
+ }
+ (*env)->CallVoidMethod(env, audioCallbackObj, android_resumeAudio);
+}
+
+void jni_terminateAudio()
+{
+ if (audioBuffer==0) return;
+ JNIEnv *env;
+ if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0)
+ {
+ (*jVM)->AttachCurrentThread(jVM,&env, NULL);
+ }
+ (*env)->CallVoidMethod(env, audioCallbackObj, android_terminateAudio);
+}
+
+
+void jni_BigScreenMode(int mode)
+{
+ JNIEnv *env;
+ if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0)
+ {
+ (*jVM)->AttachCurrentThread(jVM,&env, NULL);
+ }
+ (*env)->CallVoidMethod(env, quakeCallbackObj, android_BigScreenMode, mode);
+}
+
+
+void jni_SwitchStereoMode(int mode)
+{
+ JNIEnv *env;
+ if (((*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4))<0)
+ {
+ (*jVM)->AttachCurrentThread(jVM,&env, NULL);
+ }
+ (*env)->CallVoidMethod(env, quakeCallbackObj, android_SwitchStereoMode, mode);
+}
+
+void jni_SwitchVRMode(int mode)
+{
+ //Force headtracking on / off depending on whether we are using VR mode
+ //user must then change to their preference
+ headtracking = vrMode > 0;
+
+ JNIEnv *env;
+ jobject tmp;
+ (*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4);
+ (*env)->CallVoidMethod(env, quakeCallbackObj, android_SwitchVRMode, mode);
+}
+
+void jni_Exit()
+{
+ JNIEnv *env;
+ jobject tmp;
+ (*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4);
+ (*env)->CallVoidMethod(env, quakeCallbackObj, android_Exit);
+}
+
+//Retain the folder we are using
+char *strGameFolder = NULL;
+
+//Timing stuff for joypad control
+static long oldtime=0;
+long delta=0;
+float last_joystick_x=0;
+float last_joystick_y=0;
+
+int curtime;
+int Sys_Milliseconds (void)
+{
+ struct timeval tp;
+ struct timezone tzp;
+ static int secbase;
+
+ gettimeofday(&tp, &tzp);
+
+ if (!secbase)
+ {
+ secbase = tp.tv_sec;
+ return tp.tv_usec/1000;
+ }
+
+ curtime = (tp.tv_sec - secbase)*1000 + tp.tv_usec/1000;
+
+ return curtime;
+}
+
+int returnvalue = -1;
+void QC_exit(int exitCode)
+{
+ returnvalue = exitCode;
+ Host_Shutdown();
+ jni_Exit();
+}
+
+vec3_t hmdorientation;
+
+int JNI_OnLoad(JavaVM* vm, void* reserved)
+{
+ JNIEnv *env;
+ jVM = vm;
+ if((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK)
+ {
+ return -1;
+ }
+
+ return JNI_VERSION_1_4;
+}
+
+static void UnEscapeQuotes( char *arg )
+{
+ char *last = NULL;
+ while( *arg ) {
+ if( *arg == '"' && *last == '\\' ) {
+ char *c_curr = arg;
+ char *c_last = last;
+ while( *c_curr ) {
+ *c_last = *c_curr;
+ c_last = c_curr;
+ c_curr++;
+ }
+ *c_last = '\0';
+ }
+ last = arg;
+ arg++;
+ }
+}
+
+static int ParseCommandLine(char *cmdline, char **argv)
+{
+ char *bufp;
+ char *lastp = NULL;
+ int argc, last_argc;
+ argc = last_argc = 0;
+ for ( bufp = cmdline; *bufp; ) {
+ while ( isspace(*bufp) ) {
+ ++bufp;
+ }
+ if ( *bufp == '"' ) {
+ ++bufp;
+ if ( *bufp ) {
+ if ( argv ) {
+ argv[argc] = bufp;
+ }
+ ++argc;
+ }
+ while ( *bufp && ( *bufp != '"' || *lastp == '\\' ) ) {
+ lastp = bufp;
+ ++bufp;
+ }
+ } else {
+ if ( *bufp ) {
+ if ( argv ) {
+ argv[argc] = bufp;
+ }
+ ++argc;
+ }
+ while ( *bufp && ! isspace(*bufp) ) {
+ ++bufp;
+ }
+ }
+ if ( *bufp ) {
+ if ( argv ) {
+ *bufp = '\0';
+ }
+ ++bufp;
+ }
+ if( argv && last_argc != argc ) {
+ UnEscapeQuotes( argv[last_argc] );
+ }
+ last_argc = argc;
+ }
+ if ( argv ) {
+ argv[argc] = NULL;
+ }
+ return(argc);
+}
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_setResolution( JNIEnv * env, jobject obj, int width, int height )
+{
+ QC_SetResolution(width, height);
+}
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_initialise( JNIEnv * env, jobject obj, jstring gameFolder, jstring commandLineParams )
+{
+ static qboolean quake_initialised = false;
+ if (!quake_initialised)
+ {
+ jboolean iscopy;
+ const char * folder = (*env)->GetStringUTFChars(env, gameFolder, &iscopy);
+ chdir(folder);
+ strGameFolder = strdup(folder);
+ (*env)->ReleaseStringUTFChars(env, gameFolder, folder);
+
+ const char *arg = (*env)->GetStringUTFChars(env, commandLineParams, &iscopy);
+
+ char *cmdLine = NULL;
+ if (arg && strlen(arg))
+ {
+ cmdLine = strdup(arg);
+ }
+
+ (*env)->ReleaseStringUTFChars(env, commandLineParams, arg);
+
+ if (cmdLine)
+ {
+ char **argv;
+ int argc=0;
+ argv = malloc(sizeof(char*) * 255);
+ argc = ParseCommandLine(strdup(cmdLine), argv);
+ main(argc, argv);
+ }
+ else
+ {
+ int argc =1; char *argv[] = { "quake" };
+ main(argc, argv);
+ }
+
+ //Start game with credits active
+ MR_ToggleMenu(1);
+ quake_initialised = true;
+ }
+}
+
+#define YAW 1
+#define PITCH 0
+#define ROLL 2
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_onNewFrame( JNIEnv * env, jobject obj, float pitch, float yaw, float roll )
+{
+ long t=Sys_Milliseconds();
+ delta=t-oldtime;
+ oldtime=t;
+ if (delta>1000)
+ delta=1000;
+
+ QC_MotionEvent(delta, last_joystick_x, last_joystick_y);
+
+ //Save orientation
+ if (headtracking)
+ {
+ hmdorientation[YAW] = yaw;
+ hmdorientation[PITCH] = pitch;
+ hmdorientation[ROLL] = roll;
+ }
+ else
+ {
+ hmdorientation[YAW] = 0;
+ hmdorientation[PITCH] = 0;
+ hmdorientation[ROLL] = 0;
+
+ }
+
+ //Set move information
+ QC_MoveEvent(hmdorientation[YAW], hmdorientation[PITCH], hmdorientation[ROLL]);
+
+ //Set everything up
+ QC_BeginFrame();
+}
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_onDrawEye( JNIEnv * env, jobject obj, int eye, int x, int y )
+{
+ QC_DrawFrame(eye, x, y);
+
+ //const GLenum depthAttachment[1] = { GL_DEPTH_ATTACHMENT };
+ //glInvalidateFramebuffer( GL_FRAMEBUFFER, 1, depthAttachment );
+ //glFlush();
+}
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_onFinishFrame( JNIEnv * env, jobject obj )
+{
+ QC_EndFrame();
+}
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_onSwitchVRMode( JNIEnv * env, jobject obj, int mode )
+{
+ vrMode = mode;
+}
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_onBigScreenMode( JNIEnv * env, jobject obj, int mode )
+{
+ bigScreen = mode;
+}
+
+JNIEXPORT int JNICALL Java_com_drbeef_qvr_QVRJNILib_getCentreOffset( JNIEnv * env, jobject obj )
+{
+ return cl_autocentreoffset.integer;
+}
+
+JNIEXPORT int JNICALL Java_com_drbeef_qvr_QVRJNILib_getEyeBufferResolution( JNIEnv * env, jobject obj )
+{
+ return v_eyebufferresolution.integer;
+}
+
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_setCentreOffset( JNIEnv * env, jobject obj, int offset )
+{
+ //This is called only by the calculator, so only set it if it is not already set (i.e. by user or a previous run)
+ if (cl_autocentreoffset.integer == 0)
+ Cvar_SetValueQuick (&cl_autocentreoffset, offset);
+}
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_onKeyEvent( JNIEnv * env, jobject obj, int keyCode, int action, int character )
+{
+ //Dispatch to quake
+ QC_KeyEvent(action == AKEY_EVENT_ACTION_DOWN ? 1 : 0, keyCode, character);
+}
+
+#define SOURCE_GAMEPAD 0x00000401
+#define SOURCE_JOYSTICK 0x01000010
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_onTouchEvent( JNIEnv * env, jobject obj, int source, int action, float x, float y )
+{
+ if (source == SOURCE_JOYSTICK || source == SOURCE_GAMEPAD)
+ QC_Analog(true, x, y);
+}
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_onMotionEvent( JNIEnv * env, jobject obj, int source, int action, float x, float y )
+{
+ if (source == SOURCE_JOYSTICK || source == SOURCE_GAMEPAD)
+ {
+ last_joystick_x=x;
+ last_joystick_y=y;
+ }
+}
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_setCallbackObjects(JNIEnv *env, jclass c, jobject obj1, jobject obj2)
+{
+ jclass audioCallbackClass;
+
+ (*jVM)->GetEnv(jVM, (void**) &env, JNI_VERSION_1_4);
+
+ audioCallbackObj = (jobject)(*env)->NewGlobalRef(env, obj1);
+ audioCallbackClass = (*env)->GetObjectClass(env, audioCallbackObj);
+
+ android_initAudio = (*env)->GetMethodID(env,audioCallbackClass,"initAudio","(I)V");
+ android_writeAudio = (*env)->GetMethodID(env,audioCallbackClass,"writeAudio","(Ljava/nio/ByteBuffer;II)V");
+ android_pauseAudio = (*env)->GetMethodID(env,audioCallbackClass,"pauseAudio","()V");
+ android_resumeAudio = (*env)->GetMethodID(env,audioCallbackClass,"resumeAudio","()V");
+ android_terminateAudio = (*env)->GetMethodID(env,audioCallbackClass,"terminateAudio","()V");
+
+
+ jclass quakeCallbackClass;
+
+ quakeCallbackObj = (jobject)(*env)->NewGlobalRef(env, obj2);
+ quakeCallbackClass = (*env)->GetObjectClass(env, quakeCallbackObj);
+
+ android_BigScreenMode = (*env)->GetMethodID(env,quakeCallbackClass,"BigScreenMode","(I)V");
+ android_SwitchVRMode = (*env)->GetMethodID(env,quakeCallbackClass,"SwitchVRMode","(I)V");
+ android_SwitchStereoMode = (*env)->GetMethodID(env,quakeCallbackClass,"SwitchStereoMode","(I)V");
+ android_Exit = (*env)->GetMethodID(env,quakeCallbackClass,"Exit","()V");
+}
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_requestAudioData(JNIEnv *env, jclass c, jlong handle)
+{
+ QC_GetAudio();
+}
+
+JNIEXPORT void JNICALL Java_com_drbeef_qvr_QVRJNILib_setDownloadStatus( JNIEnv * env, jobject obj, int status )
+{
+ gameAssetsDownloadStatus = status;
+}
\ No newline at end of file
diff --git a/app/jni/SDLMain.h b/app/jni/SDLMain.h
new file mode 100644
index 0000000..4683df5
--- /dev/null
+++ b/app/jni/SDLMain.h
@@ -0,0 +1,11 @@
+/* SDLMain.m - main entry point for our Cocoa-ized SDL app
+ Initial Version: Darrell Walisser
+ Non-NIB-Code & other changes: Max Horn
+
+ Feel free to customize this file to suit your needs
+*/
+
+#import
+
+@interface SDLMain : NSObject
+@end
diff --git a/app/jni/bih.c b/app/jni/bih.c
new file mode 100644
index 0000000..7a25c66
--- /dev/null
+++ b/app/jni/bih.c
@@ -0,0 +1,216 @@
+
+// This code written in 2010 by Forest Hale (lordhavoc ghdigital com), and placed into public domain.
+
+#include
+#include
+#include "bih.h"
+
+static int BIH_BuildNode(bih_t *bih, int numchildren, int *leaflist, float *totalmins, float *totalmaxs)
+{
+ int i;
+ int j;
+ int longestaxis;
+ int axis = 0;
+ int nodenum;
+ int front = 0;
+ int back = 0;
+ bih_node_t *node;
+ bih_leaf_t *child;
+ float splitdist;
+ float d;
+ float mins[3];
+ float maxs[3];
+ float size[3];
+ float frontmins[3];
+ float frontmaxs[3];
+ float backmins[3];
+ float backmaxs[3];
+ // calculate bounds of children
+ child = bih->leafs + leaflist[0];
+ mins[0] = child->mins[0];
+ mins[1] = child->mins[1];
+ mins[2] = child->mins[2];
+ maxs[0] = child->maxs[0];
+ maxs[1] = child->maxs[1];
+ maxs[2] = child->maxs[2];
+ for (i = 1;i < numchildren;i++)
+ {
+ child = bih->leafs + leaflist[i];
+ if (mins[0] > child->mins[0]) mins[0] = child->mins[0];
+ if (mins[1] > child->mins[1]) mins[1] = child->mins[1];
+ if (mins[2] > child->mins[2]) mins[2] = child->mins[2];
+ if (maxs[0] < child->maxs[0]) maxs[0] = child->maxs[0];
+ if (maxs[1] < child->maxs[1]) maxs[1] = child->maxs[1];
+ if (maxs[2] < child->maxs[2]) maxs[2] = child->maxs[2];
+ }
+ size[0] = maxs[0] - mins[0];
+ size[1] = maxs[1] - mins[1];
+ size[2] = maxs[2] - mins[2];
+ // provide bounds to caller
+ totalmins[0] = mins[0];
+ totalmins[1] = mins[1];
+ totalmins[2] = mins[2];
+ totalmaxs[0] = maxs[0];
+ totalmaxs[1] = maxs[1];
+ totalmaxs[2] = maxs[2];
+ // if we run out of nodes it's the caller's fault, but don't crash
+ if (bih->numnodes == bih->maxnodes)
+ {
+ if (!bih->error)
+ bih->error = BIHERROR_OUT_OF_NODES;
+ return 0;
+ }
+ nodenum = bih->numnodes++;
+ node = bih->nodes + nodenum;
+ // store bounds for node
+ node->mins[0] = mins[0];
+ node->mins[1] = mins[1];
+ node->mins[2] = mins[2];
+ node->maxs[0] = maxs[0];
+ node->maxs[1] = maxs[1];
+ node->maxs[2] = maxs[2];
+ node->front = 0;
+ node->back = 0;
+ node->frontmin = 0;
+ node->backmax = 0;
+ memset(node->children, -1, sizeof(node->children));
+ // check if there are few enough children to store an unordered node
+ if (numchildren <= BIH_MAXUNORDEREDCHILDREN)
+ {
+ node->type = BIH_UNORDERED;
+ for (j = 0;j < numchildren;j++)
+ node->children[j] = leaflist[j];
+ return nodenum;
+ }
+ // pick longest axis
+ longestaxis = 0;
+ if (size[0] < size[1]) longestaxis = 1;
+ if (size[longestaxis] < size[2]) longestaxis = 2;
+ // iterate possible split axis choices, starting with the longest axis, if
+ // all fail it means all children have the same bounds and we simply split
+ // the list in half because each node can only have two children.
+ for (j = 0;j < 3;j++)
+ {
+ // pick an axis
+ axis = (longestaxis + j) % 3;
+ // sort children into front and back lists
+ splitdist = (node->mins[axis] + node->maxs[axis]) * 0.5f;
+ front = 0;
+ back = 0;
+ for (i = 0;i < numchildren;i++)
+ {
+ child = bih->leafs + leaflist[i];
+ d = (child->mins[axis] + child->maxs[axis]) * 0.5f;
+ if (d < splitdist)
+ bih->leafsortscratch[back++] = leaflist[i];
+ else
+ leaflist[front++] = leaflist[i];
+ }
+ // now copy the back ones into the space made in the leaflist for them
+ if (back)
+ memcpy(leaflist + front, bih->leafsortscratch, back*sizeof(leaflist[0]));
+ // if both sides have some children, it's good enough for us.
+ if (front && back)
+ break;
+ }
+ if (j == 3)
+ {
+ // somewhat common case: no good choice, divide children arbitrarily
+ axis = 0;
+ back = numchildren >> 1;
+ front = numchildren - back;
+ }
+
+ // we now have front and back children divided in leaflist...
+ node->type = (bih_nodetype_t)((int)BIH_SPLITX + axis);
+ node->front = BIH_BuildNode(bih, front, leaflist, frontmins, frontmaxs);
+ node->frontmin = frontmins[axis];
+ node->back = BIH_BuildNode(bih, back, leaflist + front, backmins, backmaxs);
+ node->backmax = backmaxs[axis];
+ return nodenum;
+}
+
+int BIH_Build(bih_t *bih, int numleafs, bih_leaf_t *leafs, int maxnodes, bih_node_t *nodes, int *temp_leafsort, int *temp_leafsortscratch)
+{
+ int i;
+
+ memset(bih, 0, sizeof(*bih));
+ bih->numleafs = numleafs;
+ bih->leafs = leafs;
+ bih->leafsort = temp_leafsort;
+ bih->leafsortscratch = temp_leafsortscratch;
+ bih->numnodes = 0;
+ bih->maxnodes = maxnodes;
+ bih->nodes = nodes;
+
+ // clear things we intend to rebuild
+ memset(bih->nodes, 0, sizeof(bih->nodes[0]) * bih->maxnodes);
+ for (i = 0;i < bih->numleafs;i++)
+ bih->leafsort[i] = i;
+
+ bih->rootnode = BIH_BuildNode(bih, bih->numleafs, bih->leafsort, bih->mins, bih->maxs);
+ return bih->error;
+}
+
+static void BIH_GetTriangleListForBox_Node(const bih_t *bih, int nodenum, int maxtriangles, int *trianglelist_idx, int *trianglelist_surf, int *numtrianglespointer, const float *mins, const float *maxs)
+{
+ int axis;
+ bih_node_t *node;
+ bih_leaf_t *leaf;
+ for(;;)
+ {
+ node = bih->nodes + nodenum;
+ // check if this is an unordered node (which holds an array of leaf numbers)
+ if (node->type == BIH_UNORDERED)
+ {
+ for (axis = 0;axis < BIH_MAXUNORDEREDCHILDREN && node->children[axis] >= 0;axis++)
+ {
+ leaf = bih->leafs + node->children[axis];
+ if (mins[0] > leaf->maxs[0] || maxs[0] < leaf->mins[0]
+ || mins[1] > leaf->maxs[1] || maxs[1] < leaf->mins[1]
+ || mins[2] > leaf->maxs[2] || maxs[2] < leaf->mins[2])
+ continue;
+ switch(leaf->type)
+ {
+ case BIH_RENDERTRIANGLE:
+ if (*numtrianglespointer >= maxtriangles)
+ {
+ ++*numtrianglespointer; // so the caller can detect overflow
+ break;
+ }
+ if(trianglelist_surf)
+ trianglelist_surf[*numtrianglespointer] = leaf->surfaceindex;
+ trianglelist_idx[*numtrianglespointer] = leaf->itemindex;
+ ++*numtrianglespointer;
+ break;
+ default:
+ break;
+ }
+ }
+ return;
+ }
+ // splitting node
+ axis = node->type - BIH_SPLITX;
+ if (mins[axis] < node->backmax)
+ {
+ if (maxs[axis] > node->frontmin)
+ BIH_GetTriangleListForBox_Node(bih, node->front, maxtriangles, trianglelist_idx, trianglelist_surf, numtrianglespointer, mins, maxs);
+ nodenum = node->back;
+ continue;
+ }
+ if (maxs[axis] > node->frontmin)
+ {
+ nodenum = node->front;
+ continue;
+ }
+ // fell between the child groups, nothing here
+ return;
+ }
+}
+
+int BIH_GetTriangleListForBox(const bih_t *bih, int maxtriangles, int *trianglelist_idx, int *trianglelist_surf, const float *mins, const float *maxs)
+{
+ int numtriangles = 0;
+ BIH_GetTriangleListForBox_Node(bih, bih->rootnode, maxtriangles, trianglelist_idx, trianglelist_surf, &numtriangles, mins, maxs);
+ return numtriangles;
+}
diff --git a/app/jni/bih.h b/app/jni/bih.h
new file mode 100644
index 0000000..43b659e
--- /dev/null
+++ b/app/jni/bih.h
@@ -0,0 +1,91 @@
+
+// This code written in 2010 by Forest Hale (lordhavoc ghdigital com), and placed into public domain.
+
+// Based on information in http://zach.in.tu-clausthal.de/papers/vrst02.html (in particular vrst02_boxtree.pdf)
+
+#ifndef BIH_H
+#define BIH_H
+
+#define BIH_MAXUNORDEREDCHILDREN 8
+
+typedef enum biherror_e
+{
+ BIHERROR_OK, // no error, be happy
+ BIHERROR_OUT_OF_NODES // could not produce complete hierarchy, maxnodes too low (should be roughly half of numleafs)
+}
+biherror_t;
+
+typedef enum bih_nodetype_e
+{
+ BIH_SPLITX = 0,
+ BIH_SPLITY = 1,
+ BIH_SPLITZ = 2,
+ BIH_UNORDERED = 3,
+}
+bih_nodetype_t;
+
+typedef enum bih_leaftype_e
+{
+ BIH_BRUSH = 4,
+ BIH_COLLISIONTRIANGLE = 5,
+ BIH_RENDERTRIANGLE = 6
+}
+bih_leaftype_t;
+
+typedef struct bih_node_s
+{
+ bih_nodetype_t type; // = BIH_SPLITX and similar values
+ // TODO: store just one float for distance, and have BIH_SPLITMINX and BIH_SPLITMAXX distinctions, to reduce memory footprint and traversal time, as described in the paper (vrst02_boxtree.pdf)
+ // TODO: move bounds data to parent node and remove it from leafs?
+ float mins[3];
+ float maxs[3];
+ // node indexes of children (always > this node's index)
+ int front;
+ int back;
+ // interval of children
+ float frontmin; // children[0]
+ float backmax; // children[1]
+ // BIH_UNORDERED uses this for a list of leafindex (all >= 0), -1 = end of list
+ int children[BIH_MAXUNORDEREDCHILDREN];
+}
+bih_node_t;
+
+typedef struct bih_leaf_s
+{
+ bih_leaftype_t type; // = BIH_BRUSH And similar values
+ float mins[3];
+ float maxs[3];
+ // data past this point is generic and entirely up to the caller...
+ int textureindex;
+ int surfaceindex;
+ int itemindex; // triangle or brush index
+}
+bih_leaf_t;
+
+typedef struct bih_s
+{
+ // permanent fields
+ // leafs are constructed by caller before calling BIH_Build
+ int numleafs;
+ bih_leaf_t *leafs;
+ // nodes are constructed by BIH_Build
+ int numnodes;
+ bih_node_t *nodes;
+ int rootnode; // 0 if numnodes > 0, -1 otherwise
+ // bounds calculated by BIH_Build
+ float mins[3];
+ float maxs[3];
+
+ // fields used only during BIH_Build:
+ int maxnodes;
+ int error; // set to a value if an error occurs in building (such as numnodes == maxnodes)
+ int *leafsort;
+ int *leafsortscratch;
+}
+bih_t;
+
+int BIH_Build(bih_t *bih, int numleafs, bih_leaf_t *leafs, int maxnodes, bih_node_t *nodes, int *temp_leafsort, int *temp_leafsortscratch);
+
+int BIH_GetTriangleListForBox(const bih_t *bih, int maxtriangles, int *trianglelist_idx, int *trianglelist_surf, const float *mins, const float *maxs);
+
+#endif
diff --git a/app/jni/bspfile.h b/app/jni/bspfile.h
new file mode 100644
index 0000000..e29a9f3
--- /dev/null
+++ b/app/jni/bspfile.h
@@ -0,0 +1,321 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+
+#define MAX_MAP_HULLS 16 // Q1BSP has 4, Hexen2 Q1BSP has 8, MCBSP has 16
+
+//=============================================================================
+
+
+#define BSPVERSION 29
+
+typedef struct lump_s
+{
+ int fileofs, filelen;
+} lump_t;
+
+#define LUMP_ENTITIES 0
+#define LUMP_PLANES 1
+#define LUMP_TEXTURES 2
+#define LUMP_VERTEXES 3
+#define LUMP_VISIBILITY 4
+#define LUMP_NODES 5
+#define LUMP_TEXINFO 6
+#define LUMP_FACES 7
+#define LUMP_LIGHTING 8
+#define LUMP_CLIPNODES 9
+#define LUMP_LEAFS 10
+#define LUMP_MARKSURFACES 11
+#define LUMP_EDGES 12
+#define LUMP_SURFEDGES 13
+#define LUMP_MODELS 14
+#define HEADER_LUMPS 15
+
+typedef struct hullinfo_s
+{
+ int filehulls;
+ float hullsizes[MAX_MAP_HULLS][2][3];
+} hullinfo_t;
+
+typedef struct mmodel_s
+{
+ float mins[3], maxs[3];
+ float origin[3];
+ int headnode[MAX_MAP_HULLS];
+ int visleafs; // not including the solid leaf 0
+ int firstface, numfaces;
+} mmodel_t;
+
+/*
+// WARNING: this struct does NOT match q1bsp's disk format because MAX_MAP_HULLS has been changed by Sajt's MCBSP code, this struct is only being used in memory as a result
+typedef struct dmodel_s
+{
+ float mins[3], maxs[3];
+ float origin[3];
+ int headnode[MAX_MAP_HULLS];
+ int visleafs; // not including the solid leaf 0
+ int firstface, numfaces;
+} dmodel_t;
+
+typedef struct dheader_s
+{
+ int version;
+ lump_t lumps[HEADER_LUMPS];
+} dheader_t;
+
+typedef struct dmiptexlump_s
+{
+ int nummiptex;
+ int dataofs[4]; // [nummiptex]
+} dmiptexlump_t;
+*/
+
+#define MIPLEVELS 4
+/*
+typedef struct miptex_s
+{
+ char name[16];
+ unsigned width, height;
+ unsigned offsets[MIPLEVELS]; // four mip maps stored
+} miptex_t;
+
+
+typedef struct dvertex_s
+{
+ float point[3];
+} dvertex_t;
+*/
+
+
+// 0-2 are axial planes
+#define PLANE_X 0
+#define PLANE_Y 1
+#define PLANE_Z 2
+
+// 3-5 are non-axial planes snapped to the nearest
+#define PLANE_ANYX 3
+#define PLANE_ANYY 4
+#define PLANE_ANYZ 5
+
+/*
+typedef struct dplane_s
+{
+ float normal[3];
+ float dist;
+ int type; // PLANE_X - PLANE_ANYZ ?remove? trivial to regenerate
+} dplane_t;
+*/
+
+
+// contents values in Q1 maps
+#define CONTENTS_EMPTY -1
+#define CONTENTS_SOLID -2
+#define CONTENTS_WATER -3
+#define CONTENTS_SLIME -4
+#define CONTENTS_LAVA -5
+#define CONTENTS_SKY -6
+// these were #ifdef QUAKE2 in the quake source
+#define CONTENTS_ORIGIN -7 // removed at csg time
+#define CONTENTS_CLIP -8 // changed to contents_solid
+#define CONTENTS_CURRENT_0 -9
+#define CONTENTS_CURRENT_90 -10
+#define CONTENTS_CURRENT_180 -11
+#define CONTENTS_CURRENT_270 -12
+#define CONTENTS_CURRENT_UP -13
+#define CONTENTS_CURRENT_DOWN -14
+
+//contents flags in Q2 maps
+#define CONTENTSQ2_SOLID 0x00000001 // an eye is never valid in a solid
+#define CONTENTSQ2_WINDOW 0x00000002 // translucent, but not watery
+#define CONTENTSQ2_AUX 0x00000004
+#define CONTENTSQ2_LAVA 0x00000008
+#define CONTENTSQ2_SLIME 0x00000010
+#define CONTENTSQ2_WATER 0x00000020
+#define CONTENTSQ2_MIST 0x00000040
+#define CONTENTSQ2_AREAPORTAL 0x00008000
+#define CONTENTSQ2_PLAYERCLIP 0x00010000
+#define CONTENTSQ2_MONSTERCLIP 0x00020000
+#define CONTENTSQ2_CURRENT_0 0x00040000
+#define CONTENTSQ2_CURRENT_90 0x00080000
+#define CONTENTSQ2_CURRENT_180 0x00100000
+#define CONTENTSQ2_CURRENT_270 0x00200000
+#define CONTENTSQ2_CURRENT_UP 0x00400000
+#define CONTENTSQ2_CURRENT_DOWN 0x00800000
+#define CONTENTSQ2_ORIGIN 0x01000000 // removed before bsping an entity
+#define CONTENTSQ2_MONSTER 0x02000000 // should never be on a brush, only in game
+#define CONTENTSQ2_DEADMONSTER 0x04000000
+#define CONTENTSQ2_DETAIL 0x08000000 // brushes to be added after vis leafs
+#define CONTENTSQ2_TRANSLUCENT 0x10000000 // auto set if any surface has trans
+#define CONTENTSQ2_LADDER 0x20000000
+
+//contents flags in Q3 maps
+#define CONTENTSQ3_SOLID 0x00000001 // solid (opaque and transparent)
+#define CONTENTSQ3_LAVA 0x00000008 // lava
+#define CONTENTSQ3_SLIME 0x00000010 // slime
+#define CONTENTSQ3_WATER 0x00000020 // water
+#define CONTENTSQ3_FOG 0x00000040 // unused?
+#define CONTENTSQ3_AREAPORTAL 0x00008000 // areaportal (separates areas)
+#define CONTENTSQ3_PLAYERCLIP 0x00010000 // block players
+#define CONTENTSQ3_MONSTERCLIP 0x00020000 // block monsters
+#define CONTENTSQ3_TELEPORTER 0x00040000 // hint for Q3's bots
+#define CONTENTSQ3_JUMPPAD 0x00080000 // hint for Q3's bots
+#define CONTENTSQ3_CLUSTERPORTAL 0x00100000 // hint for Q3's bots
+#define CONTENTSQ3_DONOTENTER 0x00200000 // hint for Q3's bots
+#define CONTENTSQ3_BOTCLIP 0x00400000 // hint for Q3's bots
+#define CONTENTSQ3_ORIGIN 0x01000000 // used by origin brushes to indicate origin of bmodel (removed by map compiler)
+#define CONTENTSQ3_BODY 0x02000000 // used by bbox entities (should never be on a brush)
+#define CONTENTSQ3_CORPSE 0x04000000 // used by dead bodies (SOLID_CORPSE in darkplaces)
+#define CONTENTSQ3_DETAIL 0x08000000 // brushes that do not split the bsp tree (decorations)
+#define CONTENTSQ3_STRUCTURAL 0x10000000 // brushes that split the bsp tree
+#define CONTENTSQ3_TRANSLUCENT 0x20000000 // leaves surfaces that are inside for rendering
+#define CONTENTSQ3_TRIGGER 0x40000000 // used by trigger entities
+#define CONTENTSQ3_NODROP 0x80000000 // remove items that fall into this brush
+
+#define SUPERCONTENTS_SOLID 0x00000001
+#define SUPERCONTENTS_WATER 0x00000002
+#define SUPERCONTENTS_SLIME 0x00000004
+#define SUPERCONTENTS_LAVA 0x00000008
+#define SUPERCONTENTS_SKY 0x00000010
+#define SUPERCONTENTS_BODY 0x00000020
+#define SUPERCONTENTS_CORPSE 0x00000040
+#define SUPERCONTENTS_NODROP 0x00000080
+#define SUPERCONTENTS_PLAYERCLIP 0x00000100
+#define SUPERCONTENTS_MONSTERCLIP 0x00000200
+#define SUPERCONTENTS_DONOTENTER 0x00000400
+#define SUPERCONTENTS_BOTCLIP 0x00000800
+#define SUPERCONTENTS_OPAQUE 0x00001000
+// TODO: is there any reason to define:
+// fog?
+// areaportal?
+// teleporter?
+// jumppad?
+// clusterportal?
+// detail? (div0) no, game code should not be allowed to differentiate between structural and detail
+// structural? (div0) no, game code should not be allowed to differentiate between structural and detail
+// trigger? (div0) no, as these are always solid anyway, and that's all that matters for trigger brushes
+#define SUPERCONTENTS_LIQUIDSMASK (SUPERCONTENTS_LAVA | SUPERCONTENTS_SLIME | SUPERCONTENTS_WATER)
+#define SUPERCONTENTS_VISBLOCKERMASK SUPERCONTENTS_OPAQUE
+
+/*
+#define SUPERCONTENTS_DEADMONSTER 0x00000000
+#define SUPERCONTENTS_CURRENT_0 0x00000000
+#define SUPERCONTENTS_CURRENT_90 0x00000000
+#define SUPERCONTENTS_CURRENT_180 0x00000000
+#define SUPERCONTENTS_CURRENT_270 0x00000000
+#define SUPERCONTENTS_CURRENT_DOWN 0x00000000
+#define SUPERCONTENTS_CURRENT_UP 0x00000000
+#define SUPERCONTENTS_AREAPORTAL 0x00000000
+#define SUPERCONTENTS_AUX 0x00000000
+#define SUPERCONTENTS_CLUSTERPORTAL 0x00000000
+#define SUPERCONTENTS_DETAIL 0x00000000
+#define SUPERCONTENTS_STRUCTURAL 0x00000000
+#define SUPERCONTENTS_DONOTENTER 0x00000000
+#define SUPERCONTENTS_JUMPPAD 0x00000000
+#define SUPERCONTENTS_LADDER 0x00000000
+#define SUPERCONTENTS_MONSTER 0x00000000
+#define SUPERCONTENTS_MONSTERCLIP 0x00000000
+#define SUPERCONTENTS_PLAYERCLIP 0x00000000
+#define SUPERCONTENTS_TELEPORTER 0x00000000
+#define SUPERCONTENTS_TRANSLUCENT 0x00000000
+#define SUPERCONTENTS_TRIGGER 0x00000000
+#define SUPERCONTENTS_WINDOW 0x00000000
+*/
+
+/*
+typedef struct dnode_s
+{
+ int planenum;
+ short children[2]; // negative numbers are -(leafs+1), not nodes
+ short mins[3]; // for sphere culling
+ short maxs[3];
+ unsigned short firstface;
+ unsigned short numfaces; // counting both sides
+} dnode_t;
+
+typedef struct dclipnode_s
+{
+ int planenum;
+ short children[2]; // negative numbers are contents
+} dclipnode_t;
+
+
+typedef struct texinfo_s
+{
+ float vecs[2][4]; // [s/t][xyz offset]
+ int miptex;
+ int flags;
+} texinfo_t;
+*/
+#define TEX_SPECIAL 1 // sky or slime, no lightmap or 256 subdivision
+
+// note that edge 0 is never used, because negative edge nums are used for
+// counterclockwise use of the edge in a face
+/*
+typedef struct dedge_s
+{
+ unsigned short v[2]; // vertex numbers
+} dedge_t;
+*/
+
+#define MAXLIGHTMAPS 4
+/*
+typedef struct dface_s
+{
+ // LordHavoc: changed from short to unsigned short for q2 support
+ unsigned short planenum;
+ short side;
+
+ int firstedge; // we must support > 64k edges
+ short numedges;
+ short texinfo;
+
+// lighting info
+ unsigned char styles[MAXLIGHTMAPS];
+ int lightofs; // start of [numstyles*surfsize] samples
+} dface_t;
+*/
+
+
+
+#define AMBIENT_WATER 0
+#define AMBIENT_SKY 1
+#define AMBIENT_SLIME 2
+#define AMBIENT_LAVA 3
+
+#define NUM_AMBIENTS 4 // automatic ambient sounds
+
+// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas
+// all other leafs need visibility info
+/*
+typedef struct dleaf_s
+{
+ int contents;
+ int visofs; // -1 = no visibility info
+
+ short mins[3]; // for frustum culling
+ short maxs[3];
+
+ unsigned short firstmarksurface;
+ unsigned short nummarksurfaces;
+
+ unsigned char ambient_level[NUM_AMBIENTS];
+} dleaf_t;
+*/
+
diff --git a/app/jni/build.bat b/app/jni/build.bat
new file mode 100644
index 0000000..51a72b8
--- /dev/null
+++ b/app/jni/build.bat
@@ -0,0 +1,19 @@
+call ndk-build V=0 -j10 NDK_DEBUG=0 %1
+
+cd ..\libs
+
+del libs.jar
+mkdir lib
+mkdir lib\armeabi-v7a
+copy .\armeabi-v7a\ lib\armeabi-v7a\*
+7z a -x!*.jar libs.zip .\lib*
+rename libs.zip libs.jar
+
+REM Create an archive of the source
+cd ..\src\main\assets\source
+del QVRSource.zip
+REM exclude unnecessary files from build
+7z a -r -x!.git* -x!*.o -x!*.d -x!obj -x!*.bin -x!app\build -x!app\libs -x!*.jar -x!*.so -x!*.log -x!*.jks -x!*.apk QVRSource.zip ..\..\..\..\..\*
+
+
+cd ..\..\..\..\jni
diff --git a/app/jni/builddate.c b/app/jni/builddate.c
new file mode 100644
index 0000000..061173e
--- /dev/null
+++ b/app/jni/builddate.c
@@ -0,0 +1,12 @@
+#define STRINGIFY2(arg) #arg
+#define STRINGIFY(arg) STRINGIFY2(arg)
+
+extern const char *buildstring;
+const char *buildstring = __TIME__ " " __DATE__
+#ifdef SVNREVISION
+" " STRINGIFY(SVNREVISION)
+#endif
+#ifdef BUILDTYPE
+" " STRINGIFY(BUILDTYPE)
+#endif
+;
diff --git a/app/jni/cap_avi.c b/app/jni/cap_avi.c
new file mode 100644
index 0000000..9a238e8
--- /dev/null
+++ b/app/jni/cap_avi.c
@@ -0,0 +1,720 @@
+#include "quakedef.h"
+#include "cap_avi.h"
+
+#define AVI_MASTER_INDEX_SIZE 640 // GB ought to be enough for anyone
+
+typedef struct capturevideostate_avi_formatspecific_s
+{
+ // AVI stuff
+ fs_offset_t videofile_firstchunkframes_offset;
+ fs_offset_t videofile_totalframes_offset1;
+ fs_offset_t videofile_totalframes_offset2;
+ fs_offset_t videofile_totalsampleframes_offset;
+ int videofile_ix_master_audio_inuse;
+ fs_offset_t videofile_ix_master_audio_inuse_offset;
+ fs_offset_t videofile_ix_master_audio_start_offset;
+ int videofile_ix_master_video_inuse;
+ fs_offset_t videofile_ix_master_video_inuse_offset;
+ fs_offset_t videofile_ix_master_video_start_offset;
+ fs_offset_t videofile_ix_movistart;
+ fs_offset_t position;
+ qboolean canseek;
+ sizebuf_t riffbuffer;
+ unsigned char riffbufferdata[128];
+ sizebuf_t riffindexbuffer;
+ int riffstacklevel;
+ fs_offset_t riffstackstartoffset[4];
+ fs_offset_t riffstacksizehint[4];
+ const char *riffstackfourcc[4];
+}
+capturevideostate_avi_formatspecific_t;
+#define LOAD_FORMATSPECIFIC_AVI() capturevideostate_avi_formatspecific_t *format = (capturevideostate_avi_formatspecific_t *) cls.capturevideo.formatspecific
+
+static void SCR_CaptureVideo_RIFF_Start(void)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ memset(&format->riffbuffer, 0, sizeof(sizebuf_t));
+ format->riffbuffer.maxsize = sizeof(format->riffbufferdata);
+ format->riffbuffer.data = format->riffbufferdata;
+ format->position = 0;
+}
+
+static void SCR_CaptureVideo_RIFF_Flush(void)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ if (format->riffbuffer.cursize > 0)
+ {
+ if (!FS_Write(cls.capturevideo.videofile, format->riffbuffer.data, format->riffbuffer.cursize))
+ cls.capturevideo.error = true;
+ format->position += format->riffbuffer.cursize;
+ format->riffbuffer.cursize = 0;
+ format->riffbuffer.overflowed = false;
+ }
+}
+
+static void SCR_CaptureVideo_RIFF_FlushNoIncrease(void)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ if (format->riffbuffer.cursize > 0)
+ {
+ if (!FS_Write(cls.capturevideo.videofile, format->riffbuffer.data, format->riffbuffer.cursize))
+ cls.capturevideo.error = true;
+ format->riffbuffer.cursize = 0;
+ format->riffbuffer.overflowed = false;
+ }
+}
+
+static void SCR_CaptureVideo_RIFF_WriteBytes(const unsigned char *data, size_t size)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ SCR_CaptureVideo_RIFF_Flush();
+ if (!FS_Write(cls.capturevideo.videofile, data, size))
+ cls.capturevideo.error = true;
+ format->position += size;
+}
+
+static void SCR_CaptureVideo_RIFF_Write32(int n)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ if (format->riffbuffer.cursize + 4 > format->riffbuffer.maxsize)
+ SCR_CaptureVideo_RIFF_Flush();
+ MSG_WriteLong(&format->riffbuffer, n);
+}
+
+static void SCR_CaptureVideo_RIFF_Write16(int n)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ if (format->riffbuffer.cursize + 2 > format->riffbuffer.maxsize)
+ SCR_CaptureVideo_RIFF_Flush();
+ MSG_WriteShort(&format->riffbuffer, n);
+}
+
+static void SCR_CaptureVideo_RIFF_WriteFourCC(const char *chunkfourcc)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ if (format->riffbuffer.cursize + (int)strlen(chunkfourcc) > format->riffbuffer.maxsize)
+ SCR_CaptureVideo_RIFF_Flush();
+ MSG_WriteUnterminatedString(&format->riffbuffer, chunkfourcc);
+}
+
+static void SCR_CaptureVideo_RIFF_WriteTerminatedString(const char *string)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ if (format->riffbuffer.cursize + (int)strlen(string) > format->riffbuffer.maxsize)
+ SCR_CaptureVideo_RIFF_Flush();
+ MSG_WriteString(&format->riffbuffer, string);
+}
+
+static fs_offset_t SCR_CaptureVideo_RIFF_GetPosition(void)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ SCR_CaptureVideo_RIFF_Flush();
+ //return FS_Tell(cls.capturevideo.videofile);
+ return format->position;
+}
+
+static void SCR_CaptureVideo_RIFF_Push(const char *chunkfourcc, const char *listtypefourcc, fs_offset_t sizeHint)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ if (listtypefourcc && sizeHint >= 0)
+ sizeHint += 4; // size hint is for INNER size
+ SCR_CaptureVideo_RIFF_WriteFourCC(chunkfourcc);
+ SCR_CaptureVideo_RIFF_Write32(sizeHint);
+ SCR_CaptureVideo_RIFF_Flush();
+ format->riffstacksizehint[format->riffstacklevel] = sizeHint;
+ format->riffstackstartoffset[format->riffstacklevel] = SCR_CaptureVideo_RIFF_GetPosition();
+ format->riffstackfourcc[format->riffstacklevel] = chunkfourcc;
+ ++format->riffstacklevel;
+ if (listtypefourcc)
+ SCR_CaptureVideo_RIFF_WriteFourCC(listtypefourcc);
+}
+
+static void SCR_CaptureVideo_RIFF_Pop(void)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ fs_offset_t offset, sizehint;
+ int x;
+ unsigned char sizebytes[4];
+ // write out the chunk size and then return to the current file position
+ format->riffstacklevel--;
+ offset = SCR_CaptureVideo_RIFF_GetPosition();
+
+ sizehint = format->riffstacksizehint[format->riffstacklevel];
+ x = (int)(offset - (format->riffstackstartoffset[format->riffstacklevel]));
+
+ if(x != sizehint)
+ {
+ if(sizehint != -1)
+ {
+ int i;
+ Con_Printf("WARNING: invalid size hint %d when writing video data (actual size: %d)\n", (int) sizehint, x);
+ for(i = 0; i <= format->riffstacklevel; ++i)
+ {
+ Con_Printf(" RIFF level %d = %s\n", i, format->riffstackfourcc[i]);
+ }
+ }
+ sizebytes[0] = (x) & 0xff;sizebytes[1] = (x >> 8) & 0xff;sizebytes[2] = (x >> 16) & 0xff;sizebytes[3] = (x >> 24) & 0xff;
+ if(FS_Seek(cls.capturevideo.videofile, -(x + 4), SEEK_END) >= 0)
+ {
+ FS_Write(cls.capturevideo.videofile, sizebytes, 4);
+ }
+ FS_Seek(cls.capturevideo.videofile, 0, SEEK_END);
+ }
+
+ if (offset & 1)
+ {
+ SCR_CaptureVideo_RIFF_WriteBytes((unsigned char *) "\0", 1);
+ }
+}
+
+static void GrowBuf(sizebuf_t *buf, int extralen)
+{
+ if(buf->cursize + extralen > buf->maxsize)
+ {
+ int oldsize = buf->maxsize;
+ unsigned char *olddata;
+ olddata = buf->data;
+ buf->maxsize = max(buf->maxsize * 2, 4096);
+ buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
+ if(olddata)
+ {
+ memcpy(buf->data, olddata, oldsize);
+ Mem_Free(olddata);
+ }
+ }
+}
+
+static void SCR_CaptureVideo_RIFF_IndexEntry(const char *chunkfourcc, int chunksize, int flags)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ if(!format->canseek)
+ Sys_Error("SCR_CaptureVideo_RIFF_IndexEntry called on non-seekable AVI");
+
+ if (format->riffstacklevel != 2)
+ Sys_Error("SCR_Capturevideo_RIFF_IndexEntry: RIFF stack level is %i (should be 2)\n", format->riffstacklevel);
+ GrowBuf(&format->riffindexbuffer, 16);
+ SCR_CaptureVideo_RIFF_Flush();
+ MSG_WriteUnterminatedString(&format->riffindexbuffer, chunkfourcc);
+ MSG_WriteLong(&format->riffindexbuffer, flags);
+ MSG_WriteLong(&format->riffindexbuffer, (int)FS_Tell(cls.capturevideo.videofile) - format->riffstackstartoffset[1]);
+ MSG_WriteLong(&format->riffindexbuffer, chunksize);
+}
+
+static void SCR_CaptureVideo_RIFF_MakeIxChunk(const char *fcc, const char *dwChunkId, fs_offset_t masteridx_counter, int *masteridx_count, fs_offset_t masteridx_start)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ int nMatching;
+ int i;
+ fs_offset_t ix = SCR_CaptureVideo_RIFF_GetPosition();
+ fs_offset_t pos, sz;
+
+ if(!format->canseek)
+ Sys_Error("SCR_CaptureVideo_RIFF_MakeIxChunk called on non-seekable AVI");
+
+ if(*masteridx_count >= AVI_MASTER_INDEX_SIZE)
+ return;
+
+ nMatching = 0; // go through index and enumerate them
+ for(i = 0; i < format->riffindexbuffer.cursize; i += 16)
+ if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4))
+ ++nMatching;
+
+ sz = 2+2+4+4+4+4+4;
+ for(i = 0; i < format->riffindexbuffer.cursize; i += 16)
+ if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4))
+ sz += 8;
+
+ SCR_CaptureVideo_RIFF_Push(fcc, NULL, sz);
+ SCR_CaptureVideo_RIFF_Write16(2); // wLongsPerEntry
+ SCR_CaptureVideo_RIFF_Write16(0x0100); // bIndexType=1, bIndexSubType=0
+ SCR_CaptureVideo_RIFF_Write32(nMatching); // nEntriesInUse
+ SCR_CaptureVideo_RIFF_WriteFourCC(dwChunkId); // dwChunkId
+ SCR_CaptureVideo_RIFF_Write32(format->videofile_ix_movistart & (fs_offset_t) 0xFFFFFFFFu);
+ SCR_CaptureVideo_RIFF_Write32(((fs_offset_t) format->videofile_ix_movistart) >> 32);
+ SCR_CaptureVideo_RIFF_Write32(0); // dwReserved
+
+ for(i = 0; i < format->riffindexbuffer.cursize; i += 16)
+ if(!memcmp(format->riffindexbuffer.data + i, dwChunkId, 4))
+ {
+ unsigned int *p = (unsigned int *) (format->riffindexbuffer.data + i);
+ unsigned int flags = p[1];
+ unsigned int rpos = p[2];
+ unsigned int size = p[3];
+ size &= ~0x80000000;
+ if(!(flags & 0x10)) // no keyframe?
+ size |= 0x80000000;
+ SCR_CaptureVideo_RIFF_Write32(rpos + 8);
+ SCR_CaptureVideo_RIFF_Write32(size);
+ }
+
+ SCR_CaptureVideo_RIFF_Flush();
+ SCR_CaptureVideo_RIFF_Pop();
+ pos = SCR_CaptureVideo_RIFF_GetPosition();
+
+ if(FS_Seek(cls.capturevideo.videofile, masteridx_start + 16 * *masteridx_count, SEEK_SET) >= 0)
+ {
+ SCR_CaptureVideo_RIFF_Write32(ix & (fs_offset_t) 0xFFFFFFFFu);
+ SCR_CaptureVideo_RIFF_Write32(((fs_offset_t) ix) >> 32);
+ SCR_CaptureVideo_RIFF_Write32(pos - ix);
+ SCR_CaptureVideo_RIFF_Write32(nMatching);
+ SCR_CaptureVideo_RIFF_FlushNoIncrease();
+ }
+
+ if(FS_Seek(cls.capturevideo.videofile, masteridx_counter, SEEK_SET) >= 0)
+ {
+ SCR_CaptureVideo_RIFF_Write32(++*masteridx_count);
+ SCR_CaptureVideo_RIFF_FlushNoIncrease();
+ }
+
+ FS_Seek(cls.capturevideo.videofile, 0, SEEK_END); // return value doesn't matter here
+}
+
+static void SCR_CaptureVideo_RIFF_Finish(qboolean final)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ // close the "movi" list
+ SCR_CaptureVideo_RIFF_Pop();
+ if(format->videofile_ix_master_video_inuse_offset)
+ SCR_CaptureVideo_RIFF_MakeIxChunk("ix00", "00dc", format->videofile_ix_master_video_inuse_offset, &format->videofile_ix_master_video_inuse, format->videofile_ix_master_video_start_offset);
+ if(format->videofile_ix_master_audio_inuse_offset)
+ SCR_CaptureVideo_RIFF_MakeIxChunk("ix01", "01wb", format->videofile_ix_master_audio_inuse_offset, &format->videofile_ix_master_audio_inuse, format->videofile_ix_master_audio_start_offset);
+ // write the idx1 chunk that we've been building while saving the frames (for old style players)
+ if(final && format->videofile_firstchunkframes_offset)
+ // TODO replace index creating by OpenDML ix##/##ix/indx chunk so it works for more than one AVI part too
+ {
+ SCR_CaptureVideo_RIFF_Push("idx1", NULL, format->riffindexbuffer.cursize);
+ SCR_CaptureVideo_RIFF_WriteBytes(format->riffindexbuffer.data, format->riffindexbuffer.cursize);
+ SCR_CaptureVideo_RIFF_Pop();
+ }
+ format->riffindexbuffer.cursize = 0;
+ // pop the RIFF chunk itself
+ while (format->riffstacklevel > 0)
+ SCR_CaptureVideo_RIFF_Pop();
+ SCR_CaptureVideo_RIFF_Flush();
+ if(format->videofile_firstchunkframes_offset)
+ {
+ Con_DPrintf("Finishing first chunk (%d frames)\n", cls.capturevideo.frame);
+ if(FS_Seek(cls.capturevideo.videofile, format->videofile_firstchunkframes_offset, SEEK_SET) >= 0)
+ {
+ SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame);
+ SCR_CaptureVideo_RIFF_FlushNoIncrease();
+ }
+ FS_Seek(cls.capturevideo.videofile, 0, SEEK_END);
+ format->videofile_firstchunkframes_offset = 0;
+ }
+ else
+ Con_DPrintf("Finishing another chunk (%d frames)\n", cls.capturevideo.frame);
+}
+
+static void SCR_CaptureVideo_RIFF_OverflowCheck(int framesize)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ fs_offset_t cursize;
+ //fs_offset_t curfilesize;
+ if (format->riffstacklevel != 2)
+ Sys_Error("SCR_CaptureVideo_RIFF_OverflowCheck: chunk stack leakage!\n");
+
+ if(!format->canseek)
+ return;
+
+ // check where we are in the file
+ SCR_CaptureVideo_RIFF_Flush();
+ cursize = SCR_CaptureVideo_RIFF_GetPosition() - format->riffstackstartoffset[0];
+ //curfilesize = SCR_CaptureVideo_RIFF_GetPosition();
+
+ // if this would overflow the windows limit of 1GB per RIFF chunk, we need
+ // to close the current RIFF chunk and open another for future frames
+ if (8 + cursize + framesize + format->riffindexbuffer.cursize + 8 + format->riffindexbuffer.cursize + 64 > 1<<30) // note that the Ix buffer takes less space... I just don't dare to / 2 here now... sorry, maybe later
+ {
+ SCR_CaptureVideo_RIFF_Finish(false);
+ // begin a new 1GB extended section of the AVI
+ SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", -1);
+ SCR_CaptureVideo_RIFF_Push("LIST", "movi", -1);
+ format->videofile_ix_movistart = format->riffstackstartoffset[1];
+ }
+}
+
+// converts from BGRA32 to I420 colorspace (identical to YV12 except chroma plane order is reversed), this colorspace is handled by the Intel(r) 4:2:0 codec on Windows
+static void SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(int width, int height, unsigned char *instart, unsigned char *outstart)
+{
+ int x, y;
+ int blockr, blockg, blockb;
+ int outoffset = (width/2)*(height/2);
+ unsigned char *b, *out;
+ // process one line at a time, and CbCr every other line at 2 pixel intervals
+ for (y = 0;y < height;y++)
+ {
+ // 1x1 Y
+ for (b = instart + (height-1-y)*width*4, out = outstart + y*width, x = 0;x < width;x++, b += 4, out++)
+ {
+ blockr = b[2];
+ blockg = b[1];
+ blockb = b[0];
+ *out = cls.capturevideo.yuvnormalizetable[0][cls.capturevideo.rgbtoyuvscaletable[0][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[0][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[0][2][blockb]];
+ }
+ if ((y & 1) == 0 && y/2 < height/2) // if h is odd, this skips the last row
+ {
+ // 2x2 Cr and Cb planes
+ int inpitch = width*4;
+ for (b = instart + (height-2-y)*width*4, out = outstart + width*height + (y/2)*(width/2), x = 0;x < width/2;x++, b += 8, out++)
+ {
+ blockr = (b[2] + b[6] + b[inpitch+2] + b[inpitch+6]) >> 2;
+ blockg = (b[1] + b[5] + b[inpitch+1] + b[inpitch+5]) >> 2;
+ blockb = (b[0] + b[4] + b[inpitch+0] + b[inpitch+4]) >> 2;
+ // Cr
+ out[0 ] = cls.capturevideo.yuvnormalizetable[1][cls.capturevideo.rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[1][2][blockb] + 128];
+ // Cb
+ out[outoffset] = cls.capturevideo.yuvnormalizetable[2][cls.capturevideo.rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[2][2][blockb] + 128];
+ }
+ }
+ }
+}
+
+static void SCR_CaptureVideo_Avi_VideoFrames(int num)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ int x = 0, width = cls.capturevideo.width, height = cls.capturevideo.height;
+ unsigned char *in, *out;
+ // FIXME: width/height must be multiple of 2, enforce this?
+ in = cls.capturevideo.outbuffer;
+ out = cls.capturevideo.outbuffer + width*height*4;
+ SCR_CaptureVideo_ConvertFrame_BGRA_to_I420_flip(width, height, in, out);
+ x = width*height+(width/2)*(height/2)*2;
+ while(num-- > 0)
+ {
+ if(format->canseek)
+ {
+ SCR_CaptureVideo_RIFF_OverflowCheck(8 + x);
+ SCR_CaptureVideo_RIFF_IndexEntry("00dc", x, 0x10); // AVIIF_KEYFRAME
+ }
+
+ if(!format->canseek)
+ {
+ SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", 12+8+x);
+ SCR_CaptureVideo_RIFF_Push("LIST", "movi", 8+x);
+ }
+ SCR_CaptureVideo_RIFF_Push("00dc", NULL, x);
+ SCR_CaptureVideo_RIFF_WriteBytes(out, x);
+ SCR_CaptureVideo_RIFF_Pop();
+ if(!format->canseek)
+ {
+ SCR_CaptureVideo_RIFF_Pop();
+ SCR_CaptureVideo_RIFF_Pop();
+ }
+ }
+}
+
+static void SCR_CaptureVideo_Avi_EndVideo(void)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+
+ if(format->canseek)
+ {
+ // close any open chunks
+ SCR_CaptureVideo_RIFF_Finish(true);
+
+ // go back and fix the video frames and audio samples fields
+ if(format->videofile_totalframes_offset1)
+ if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalframes_offset1, SEEK_SET) >= 0)
+ {
+ SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame);
+ SCR_CaptureVideo_RIFF_FlushNoIncrease();
+ }
+ if(format->videofile_totalframes_offset2)
+ if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalframes_offset2, SEEK_SET) >= 0)
+ {
+ SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.frame);
+ SCR_CaptureVideo_RIFF_FlushNoIncrease();
+ }
+ if (cls.capturevideo.soundrate)
+ {
+ if(format->videofile_totalsampleframes_offset)
+ if(FS_Seek(cls.capturevideo.videofile, format->videofile_totalsampleframes_offset, SEEK_SET) >= 0)
+ {
+ SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundsampleframe);
+ SCR_CaptureVideo_RIFF_FlushNoIncrease();
+ }
+ }
+ }
+
+ if (format->riffindexbuffer.data)
+ {
+ Mem_Free(format->riffindexbuffer.data);
+ format->riffindexbuffer.data = NULL;
+ }
+
+ FS_Close(cls.capturevideo.videofile);
+ cls.capturevideo.videofile = NULL;
+
+ Mem_Free(format);
+}
+
+static void SCR_CaptureVideo_Avi_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length)
+{
+ LOAD_FORMATSPECIFIC_AVI();
+ int x;
+ unsigned char bufstereo16le[PAINTBUFFER_SIZE * 4];
+ unsigned char* out_ptr;
+ size_t i;
+
+ // write the sound buffer as little endian 16bit interleaved stereo
+ for(i = 0, out_ptr = bufstereo16le; i < length; i++, out_ptr += 4)
+ {
+ int n0, n1;
+
+ n0 = paintbuffer[i].sample[0] * 32768.0f;
+ n0 = bound(-32768, n0, 32767);
+ out_ptr[0] = (unsigned char)n0;
+ out_ptr[1] = (unsigned char)(n0 >> 8);
+
+ n1 = paintbuffer[i].sample[1] * 32768.0f;
+ n1 = bound(-32768, n1, 32767);
+ out_ptr[2] = (unsigned char)n1;
+ out_ptr[3] = (unsigned char)(n1 >> 8);
+ }
+
+ x = length*4;
+ if(format->canseek)
+ {
+ SCR_CaptureVideo_RIFF_OverflowCheck(8 + x);
+ SCR_CaptureVideo_RIFF_IndexEntry("01wb", x, 0x10); // AVIIF_KEYFRAME
+ }
+
+ if(!format->canseek)
+ {
+ SCR_CaptureVideo_RIFF_Push("RIFF", "AVIX", 12+8+x);
+ SCR_CaptureVideo_RIFF_Push("LIST", "movi", 8+x);
+ }
+ SCR_CaptureVideo_RIFF_Push("01wb", NULL, x);
+ SCR_CaptureVideo_RIFF_WriteBytes(bufstereo16le, x);
+ SCR_CaptureVideo_RIFF_Pop();
+ if(!format->canseek)
+ {
+ SCR_CaptureVideo_RIFF_Pop();
+ SCR_CaptureVideo_RIFF_Pop();
+ }
+}
+
+void SCR_CaptureVideo_Avi_BeginVideo(void)
+{
+ int width = cls.capturevideo.width;
+ int height = cls.capturevideo.height;
+ int n, d;
+ unsigned int i;
+ double aspect;
+ char vabuf[1024];
+
+ aspect = vid.width / (vid.height * vid_pixelheight.value);
+
+ cls.capturevideo.format = CAPTUREVIDEOFORMAT_AVI_I420;
+ cls.capturevideo.formatextension = "avi";
+ cls.capturevideo.videofile = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false);
+ cls.capturevideo.endvideo = SCR_CaptureVideo_Avi_EndVideo;
+ cls.capturevideo.videoframes = SCR_CaptureVideo_Avi_VideoFrames;
+ cls.capturevideo.soundframe = SCR_CaptureVideo_Avi_SoundFrame;
+ cls.capturevideo.formatspecific = Mem_Alloc(tempmempool, sizeof(capturevideostate_avi_formatspecific_t));
+ {
+ LOAD_FORMATSPECIFIC_AVI();
+ format->canseek = (FS_Seek(cls.capturevideo.videofile, 0, SEEK_SET) == 0);
+ SCR_CaptureVideo_RIFF_Start();
+ // enclosing RIFF chunk (there can be multiple of these in >1GB files, the later ones are "AVIX" instead of "AVI " and have no header/stream info)
+ SCR_CaptureVideo_RIFF_Push("RIFF", "AVI ", format->canseek ? -1 : 12+(8+56+12+(12+52+8+40+8+68)+(cls.capturevideo.soundrate?(12+12+52+8+18):0)+12+(8+4))+12+(8+(((int) strlen(engineversion) | 1) + 1))+12);
+ // AVI main header
+ SCR_CaptureVideo_RIFF_Push("LIST", "hdrl", format->canseek ? -1 : 8+56+12+(12+52+8+40+8+68)+(cls.capturevideo.soundrate?(12+12+52+8+18):0)+12+(8+4));
+ SCR_CaptureVideo_RIFF_Push("avih", NULL, 56);
+ SCR_CaptureVideo_RIFF_Write32((int)(1000000.0 / (cls.capturevideo.framerate / cls.capturevideo.framestep))); // microseconds per frame
+ SCR_CaptureVideo_RIFF_Write32(0); // max bytes per second
+ SCR_CaptureVideo_RIFF_Write32(0); // padding granularity
+ SCR_CaptureVideo_RIFF_Write32(0x910); // flags (AVIF_HASINDEX | AVIF_ISINTERLEAVED | AVIF_TRUSTCKTYPE)
+ format->videofile_firstchunkframes_offset = SCR_CaptureVideo_RIFF_GetPosition();
+ SCR_CaptureVideo_RIFF_Write32(0); // total frames
+ SCR_CaptureVideo_RIFF_Write32(0); // initial frames
+ if (cls.capturevideo.soundrate)
+ SCR_CaptureVideo_RIFF_Write32(2); // number of streams
+ else
+ SCR_CaptureVideo_RIFF_Write32(1); // number of streams
+ SCR_CaptureVideo_RIFF_Write32(0); // suggested buffer size
+ SCR_CaptureVideo_RIFF_Write32(width); // width
+ SCR_CaptureVideo_RIFF_Write32(height); // height
+ SCR_CaptureVideo_RIFF_Write32(0); // reserved[0]
+ SCR_CaptureVideo_RIFF_Write32(0); // reserved[1]
+ SCR_CaptureVideo_RIFF_Write32(0); // reserved[2]
+ SCR_CaptureVideo_RIFF_Write32(0); // reserved[3]
+ SCR_CaptureVideo_RIFF_Pop();
+ // video stream info
+ SCR_CaptureVideo_RIFF_Push("LIST", "strl", format->canseek ? -1 : 12+52+8+40+8+68);
+ SCR_CaptureVideo_RIFF_Push("strh", "vids", 52);
+ SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // stream fourcc (I420 colorspace, uncompressed)
+ SCR_CaptureVideo_RIFF_Write32(0); // flags
+ SCR_CaptureVideo_RIFF_Write16(0); // priority
+ SCR_CaptureVideo_RIFF_Write16(0); // language
+ SCR_CaptureVideo_RIFF_Write32(0); // initial frames
+ // find an ideal divisor for the framerate
+ FindFraction(cls.capturevideo.framerate / cls.capturevideo.framestep, &n, &d, 1000);
+ SCR_CaptureVideo_RIFF_Write32(d); // samples/second divisor
+ SCR_CaptureVideo_RIFF_Write32(n); // samples/second multiplied by divisor
+ SCR_CaptureVideo_RIFF_Write32(0); // start
+ format->videofile_totalframes_offset1 = SCR_CaptureVideo_RIFF_GetPosition();
+ SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF); // length
+ SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // suggested buffer size
+ SCR_CaptureVideo_RIFF_Write32(0); // quality
+ SCR_CaptureVideo_RIFF_Write32(0); // sample size
+ SCR_CaptureVideo_RIFF_Write16(0); // frame left
+ SCR_CaptureVideo_RIFF_Write16(0); // frame top
+ SCR_CaptureVideo_RIFF_Write16(width); // frame right
+ SCR_CaptureVideo_RIFF_Write16(height); // frame bottom
+ SCR_CaptureVideo_RIFF_Pop();
+ // video stream format
+ SCR_CaptureVideo_RIFF_Push("strf", NULL, 40);
+ SCR_CaptureVideo_RIFF_Write32(40); // BITMAPINFO struct size
+ SCR_CaptureVideo_RIFF_Write32(width); // width
+ SCR_CaptureVideo_RIFF_Write32(height); // height
+ SCR_CaptureVideo_RIFF_Write16(3); // planes
+ SCR_CaptureVideo_RIFF_Write16(12); // bitcount
+ SCR_CaptureVideo_RIFF_WriteFourCC("I420"); // compression
+ SCR_CaptureVideo_RIFF_Write32(width*height+(width/2)*(height/2)*2); // size of image
+ SCR_CaptureVideo_RIFF_Write32(0); // x pixels per meter
+ SCR_CaptureVideo_RIFF_Write32(0); // y pixels per meter
+ SCR_CaptureVideo_RIFF_Write32(0); // color used
+ SCR_CaptureVideo_RIFF_Write32(0); // color important
+ SCR_CaptureVideo_RIFF_Pop();
+ // master index
+ if(format->canseek)
+ {
+ SCR_CaptureVideo_RIFF_Push("indx", NULL, -1);
+ SCR_CaptureVideo_RIFF_Write16(4); // wLongsPerEntry
+ SCR_CaptureVideo_RIFF_Write16(0); // bIndexSubType=0, bIndexType=0
+ format->videofile_ix_master_video_inuse_offset = SCR_CaptureVideo_RIFF_GetPosition();
+ SCR_CaptureVideo_RIFF_Write32(0); // nEntriesInUse
+ SCR_CaptureVideo_RIFF_WriteFourCC("00dc"); // dwChunkId
+ SCR_CaptureVideo_RIFF_Write32(0); // dwReserved1
+ SCR_CaptureVideo_RIFF_Write32(0); // dwReserved2
+ SCR_CaptureVideo_RIFF_Write32(0); // dwReserved3
+ format->videofile_ix_master_video_start_offset = SCR_CaptureVideo_RIFF_GetPosition();
+ for(i = 0; i < AVI_MASTER_INDEX_SIZE * 4; ++i)
+ SCR_CaptureVideo_RIFF_Write32(0); // fill up later
+ SCR_CaptureVideo_RIFF_Pop();
+ }
+ // extended format (aspect!)
+ SCR_CaptureVideo_RIFF_Push("vprp", NULL, 68);
+ SCR_CaptureVideo_RIFF_Write32(0); // VideoFormatToken
+ SCR_CaptureVideo_RIFF_Write32(0); // VideoStandard
+ SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.framerate / cls.capturevideo.framestep)); // dwVerticalRefreshRate (bogus)
+ SCR_CaptureVideo_RIFF_Write32(width); // dwHTotalInT
+ SCR_CaptureVideo_RIFF_Write32(height); // dwVTotalInLines
+ FindFraction(aspect, &n, &d, 1000);
+ SCR_CaptureVideo_RIFF_Write32((n << 16) | d); // dwFrameAspectRatio // TODO a word
+ SCR_CaptureVideo_RIFF_Write32(width); // dwFrameWidthInPixels
+ SCR_CaptureVideo_RIFF_Write32(height); // dwFrameHeightInLines
+ SCR_CaptureVideo_RIFF_Write32(1); // nFieldPerFrame
+ SCR_CaptureVideo_RIFF_Write32(width); // CompressedBMWidth
+ SCR_CaptureVideo_RIFF_Write32(height); // CompressedBMHeight
+ SCR_CaptureVideo_RIFF_Write32(width); // ValidBMHeight
+ SCR_CaptureVideo_RIFF_Write32(height); // ValidBMWidth
+ SCR_CaptureVideo_RIFF_Write32(0); // ValidBMXOffset
+ SCR_CaptureVideo_RIFF_Write32(0); // ValidBMYOffset
+ SCR_CaptureVideo_RIFF_Write32(0); // ValidBMXOffsetInT
+ SCR_CaptureVideo_RIFF_Write32(0); // ValidBMYValidStartLine
+ SCR_CaptureVideo_RIFF_Pop();
+ SCR_CaptureVideo_RIFF_Pop();
+ if (cls.capturevideo.soundrate)
+ {
+ // audio stream info
+ SCR_CaptureVideo_RIFF_Push("LIST", "strl", format->canseek ? -1 : 12+52+8+18);
+ SCR_CaptureVideo_RIFF_Push("strh", "auds", 52);
+ SCR_CaptureVideo_RIFF_Write32(1); // stream fourcc (PCM audio, uncompressed)
+ SCR_CaptureVideo_RIFF_Write32(0); // flags
+ SCR_CaptureVideo_RIFF_Write16(0); // priority
+ SCR_CaptureVideo_RIFF_Write16(0); // language
+ SCR_CaptureVideo_RIFF_Write32(0); // initial frames
+ SCR_CaptureVideo_RIFF_Write32(1); // samples/second divisor
+ SCR_CaptureVideo_RIFF_Write32((int)(cls.capturevideo.soundrate)); // samples/second multiplied by divisor
+ SCR_CaptureVideo_RIFF_Write32(0); // start
+ format->videofile_totalsampleframes_offset = SCR_CaptureVideo_RIFF_GetPosition();
+ SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF); // length
+ SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 2); // suggested buffer size (this is a half second)
+ SCR_CaptureVideo_RIFF_Write32(0); // quality
+ SCR_CaptureVideo_RIFF_Write32(4); // sample size
+ SCR_CaptureVideo_RIFF_Write16(0); // frame left
+ SCR_CaptureVideo_RIFF_Write16(0); // frame top
+ SCR_CaptureVideo_RIFF_Write16(0); // frame right
+ SCR_CaptureVideo_RIFF_Write16(0); // frame bottom
+ SCR_CaptureVideo_RIFF_Pop();
+ // audio stream format
+ SCR_CaptureVideo_RIFF_Push("strf", NULL, 18);
+ SCR_CaptureVideo_RIFF_Write16(1); // format (uncompressed PCM?)
+ SCR_CaptureVideo_RIFF_Write16(2); // channels (stereo)
+ SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate); // sampleframes per second
+ SCR_CaptureVideo_RIFF_Write32(cls.capturevideo.soundrate * 4); // average bytes per second
+ SCR_CaptureVideo_RIFF_Write16(4); // block align
+ SCR_CaptureVideo_RIFF_Write16(16); // bits per sample
+ SCR_CaptureVideo_RIFF_Write16(0); // size
+ SCR_CaptureVideo_RIFF_Pop();
+ // master index
+ if(format->canseek)
+ {
+ SCR_CaptureVideo_RIFF_Push("indx", NULL, -1);
+ SCR_CaptureVideo_RIFF_Write16(4); // wLongsPerEntry
+ SCR_CaptureVideo_RIFF_Write16(0); // bIndexSubType=0, bIndexType=0
+ format->videofile_ix_master_audio_inuse_offset = SCR_CaptureVideo_RIFF_GetPosition();
+ SCR_CaptureVideo_RIFF_Write32(0); // nEntriesInUse
+ SCR_CaptureVideo_RIFF_WriteFourCC("01wb"); // dwChunkId
+ SCR_CaptureVideo_RIFF_Write32(0); // dwReserved1
+ SCR_CaptureVideo_RIFF_Write32(0); // dwReserved2
+ SCR_CaptureVideo_RIFF_Write32(0); // dwReserved3
+ format->videofile_ix_master_audio_start_offset = SCR_CaptureVideo_RIFF_GetPosition();
+ for(i = 0; i < AVI_MASTER_INDEX_SIZE * 4; ++i)
+ SCR_CaptureVideo_RIFF_Write32(0); // fill up later
+ SCR_CaptureVideo_RIFF_Pop();
+ }
+ SCR_CaptureVideo_RIFF_Pop();
+ }
+
+ format->videofile_ix_master_audio_inuse = format->videofile_ix_master_video_inuse = 0;
+
+ // extended header (for total #frames)
+ SCR_CaptureVideo_RIFF_Push("LIST", "odml", 8+4);
+ SCR_CaptureVideo_RIFF_Push("dmlh", NULL, 4);
+ format->videofile_totalframes_offset2 = SCR_CaptureVideo_RIFF_GetPosition();
+ SCR_CaptureVideo_RIFF_Write32(0xFFFFFFFF);
+ SCR_CaptureVideo_RIFF_Pop();
+ SCR_CaptureVideo_RIFF_Pop();
+
+ // close the AVI header list
+ SCR_CaptureVideo_RIFF_Pop();
+ // software that produced this AVI video file
+ SCR_CaptureVideo_RIFF_Push("LIST", "INFO", 8+((strlen(engineversion) | 1) + 1));
+ SCR_CaptureVideo_RIFF_Push("ISFT", NULL, strlen(engineversion) + 1);
+ SCR_CaptureVideo_RIFF_WriteTerminatedString(engineversion);
+ SCR_CaptureVideo_RIFF_Pop();
+ // enable this junk filler if you like the LIST movi to always begin at 4KB in the file (why?)
+#if 0
+ SCR_CaptureVideo_RIFF_Push("JUNK", NULL);
+ x = 4096 - SCR_CaptureVideo_RIFF_GetPosition();
+ while (x > 0)
+ {
+ const char *junkfiller = "[ DarkPlaces junk data ]";
+ int i = min(x, (int)strlen(junkfiller));
+ SCR_CaptureVideo_RIFF_WriteBytes((const unsigned char *)junkfiller, i);
+ x -= i;
+ }
+ SCR_CaptureVideo_RIFF_Pop();
+#endif
+ SCR_CaptureVideo_RIFF_Pop();
+ // begin the actual video section now
+ SCR_CaptureVideo_RIFF_Push("LIST", "movi", format->canseek ? -1 : 0);
+ format->videofile_ix_movistart = format->riffstackstartoffset[1];
+ // we're done with the headers now...
+ SCR_CaptureVideo_RIFF_Flush();
+ if (format->riffstacklevel != 2)
+ Sys_Error("SCR_CaptureVideo_BeginVideo: broken AVI writing code (stack level is %i (should be 2) at end of headers)\n", format->riffstacklevel);
+
+ if(!format->canseek)
+ {
+ // close the movi immediately
+ SCR_CaptureVideo_RIFF_Pop();
+ // close the AVI immediately (we'll put all frames into AVIX)
+ SCR_CaptureVideo_RIFF_Pop();
+ }
+ }
+}
diff --git a/app/jni/cap_avi.h b/app/jni/cap_avi.h
new file mode 100644
index 0000000..2cf15d6
--- /dev/null
+++ b/app/jni/cap_avi.h
@@ -0,0 +1 @@
+void SCR_CaptureVideo_Avi_BeginVideo(void);
diff --git a/app/jni/cap_ogg.c b/app/jni/cap_ogg.c
new file mode 100644
index 0000000..a4913ae
--- /dev/null
+++ b/app/jni/cap_ogg.c
@@ -0,0 +1,1121 @@
+#ifndef _MSC_VER
+#include
+#endif
+#include
+
+#include "quakedef.h"
+#include "client.h"
+#include "cap_ogg.h"
+
+// video capture cvars
+static cvar_t cl_capturevideo_ogg_theora_vp3compat = {CVAR_SAVE, "cl_capturevideo_ogg_theora_vp3compat", "1", "make VP3 compatible theora streams"};
+static cvar_t cl_capturevideo_ogg_theora_quality = {CVAR_SAVE, "cl_capturevideo_ogg_theora_quality", "48", "video quality factor (0 to 63), or -1 to use bitrate only; higher is better; setting both to -1 achieves unlimited quality"};
+static cvar_t cl_capturevideo_ogg_theora_bitrate = {CVAR_SAVE, "cl_capturevideo_ogg_theora_bitrate", "-1", "video bitrate (45 to 2000 kbps), or -1 to use quality only; higher is better; setting both to -1 achieves unlimited quality"};
+static cvar_t cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier", "1.5", "how much more bit rate to use for keyframes, specified as a factor of at least 1"};
+static cvar_t cl_capturevideo_ogg_theora_keyframe_maxinterval = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_maxinterval", "64", "maximum keyframe interval (1 to 1000)"};
+static cvar_t cl_capturevideo_ogg_theora_keyframe_mininterval = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_mininterval", "8", "minimum keyframe interval (1 to 1000)"};
+static cvar_t cl_capturevideo_ogg_theora_keyframe_auto_threshold = {CVAR_SAVE, "cl_capturevideo_ogg_theora_keyframe_auto_threshold", "80", "threshold for key frame decision (0 to 100)"};
+static cvar_t cl_capturevideo_ogg_theora_noise_sensitivity = {CVAR_SAVE, "cl_capturevideo_ogg_theora_noise_sensitivity", "1", "video noise sensitivity (0 to 6); lower is better"};
+static cvar_t cl_capturevideo_ogg_theora_sharpness = {CVAR_SAVE, "cl_capturevideo_ogg_theora_sharpness", "0", "sharpness (0 to 2); lower is sharper"};
+static cvar_t cl_capturevideo_ogg_vorbis_quality = {CVAR_SAVE, "cl_capturevideo_ogg_vorbis_quality", "3", "audio quality (-1 to 10); higher is better"};
+
+// ogg.h stuff
+#ifdef _MSC_VER
+typedef __int16 ogg_int16_t;
+typedef unsigned __int16 ogg_uint16_t;
+typedef __int32 ogg_int32_t;
+typedef unsigned __int32 ogg_uint32_t;
+typedef __int64 ogg_int64_t;
+#else
+typedef int16_t ogg_int16_t;
+typedef uint16_t ogg_uint16_t;
+typedef int32_t ogg_int32_t;
+typedef uint32_t ogg_uint32_t;
+typedef int64_t ogg_int64_t;
+#endif
+
+typedef struct {
+ long endbyte;
+ int endbit;
+
+ unsigned char *buffer;
+ unsigned char *ptr;
+ long storage;
+} oggpack_buffer;
+
+/* ogg_page is used to encapsulate the data in one Ogg bitstream page *****/
+
+typedef struct {
+ unsigned char *header;
+ long header_len;
+ unsigned char *body;
+ long body_len;
+} ogg_page;
+
+/* ogg_stream_state contains the current encode/decode state of a logical
+ Ogg bitstream **********************************************************/
+
+typedef struct {
+ unsigned char *body_data; /* bytes from packet bodies */
+ long body_storage; /* storage elements allocated */
+ long body_fill; /* elements stored; fill mark */
+ long body_returned; /* elements of fill returned */
+
+
+ int *lacing_vals; /* The values that will go to the segment table */
+ ogg_int64_t *granule_vals; /* granulepos values for headers. Not compact
+ this way, but it is simple coupled to the
+ lacing fifo */
+ long lacing_storage;
+ long lacing_fill;
+ long lacing_packet;
+ long lacing_returned;
+
+ unsigned char header[282]; /* working space for header encode */
+ int header_fill;
+
+ int e_o_s; /* set when we have buffered the last packet in the
+ logical bitstream */
+ int b_o_s; /* set after we've written the initial page
+ of a logical bitstream */
+ long serialno;
+ long pageno;
+ ogg_int64_t packetno; /* sequence number for decode; the framing
+ knows where there's a hole in the data,
+ but we need coupling so that the codec
+ (which is in a seperate abstraction
+ layer) also knows about the gap */
+ ogg_int64_t granulepos;
+
+} ogg_stream_state;
+
+/* ogg_packet is used to encapsulate the data and metadata belonging
+ to a single raw Ogg/Vorbis packet *************************************/
+
+typedef struct {
+ unsigned char *packet;
+ long bytes;
+ long b_o_s;
+ long e_o_s;
+
+ ogg_int64_t granulepos;
+
+ ogg_int64_t packetno; /* sequence number for decode; the framing
+ knows where there's a hole in the data,
+ but we need coupling so that the codec
+ (which is in a seperate abstraction
+ layer) also knows about the gap */
+} ogg_packet;
+
+typedef struct {
+ unsigned char *data;
+ int storage;
+ int fill;
+ int returned;
+
+ int unsynced;
+ int headerbytes;
+ int bodybytes;
+} ogg_sync_state;
+
+/* Ogg BITSTREAM PRIMITIVES: encoding **************************/
+
+static int (*qogg_stream_packetin) (ogg_stream_state *os, ogg_packet *op);
+static int (*qogg_stream_pageout) (ogg_stream_state *os, ogg_page *og);
+static int (*qogg_stream_flush) (ogg_stream_state *os, ogg_page *og);
+
+/* Ogg BITSTREAM PRIMITIVES: general ***************************/
+
+static int (*qogg_stream_init) (ogg_stream_state *os,int serialno);
+static int (*qogg_stream_clear) (ogg_stream_state *os);
+static ogg_int64_t (*qogg_page_granulepos) (ogg_page *og);
+
+// end of ogg.h stuff
+
+// vorbis/codec.h stuff
+typedef struct vorbis_info{
+ int version;
+ int channels;
+ long rate;
+
+ /* The below bitrate declarations are *hints*.
+ Combinations of the three values carry the following implications:
+
+ all three set to the same value:
+ implies a fixed rate bitstream
+ only nominal set:
+ implies a VBR stream that averages the nominal bitrate. No hard
+ upper/lower limit
+ upper and or lower set:
+ implies a VBR bitstream that obeys the bitrate limits. nominal
+ may also be set to give a nominal rate.
+ none set:
+ the coder does not care to speculate.
+ */
+
+ long bitrate_upper;
+ long bitrate_nominal;
+ long bitrate_lower;
+ long bitrate_window;
+
+ void *codec_setup;
+} vorbis_info;
+
+/* vorbis_dsp_state buffers the current vorbis audio
+ analysis/synthesis state. The DSP state belongs to a specific
+ logical bitstream ****************************************************/
+typedef struct vorbis_dsp_state{
+ int analysisp;
+ vorbis_info *vi;
+
+ float **pcm;
+ float **pcmret;
+ int pcm_storage;
+ int pcm_current;
+ int pcm_returned;
+
+ int preextrapolate;
+ int eofflag;
+
+ long lW;
+ long W;
+ long nW;
+ long centerW;
+
+ ogg_int64_t granulepos;
+ ogg_int64_t sequence;
+
+ ogg_int64_t glue_bits;
+ ogg_int64_t time_bits;
+ ogg_int64_t floor_bits;
+ ogg_int64_t res_bits;
+
+ void *backend_state;
+} vorbis_dsp_state;
+
+typedef struct vorbis_block{
+ /* necessary stream state for linking to the framing abstraction */
+ float **pcm; /* this is a pointer into local storage */
+ oggpack_buffer opb;
+
+ long lW;
+ long W;
+ long nW;
+ int pcmend;
+ int mode;
+
+ int eofflag;
+ ogg_int64_t granulepos;
+ ogg_int64_t sequence;
+ vorbis_dsp_state *vd; /* For read-only access of configuration */
+
+ /* local storage to avoid remallocing; it's up to the mapping to
+ structure it */
+ void *localstore;
+ long localtop;
+ long localalloc;
+ long totaluse;
+ struct alloc_chain *reap;
+
+ /* bitmetrics for the frame */
+ long glue_bits;
+ long time_bits;
+ long floor_bits;
+ long res_bits;
+
+ void *internal;
+
+} vorbis_block;
+
+/* vorbis_block is a single block of data to be processed as part of
+the analysis/synthesis stream; it belongs to a specific logical
+bitstream, but is independant from other vorbis_blocks belonging to
+that logical bitstream. *************************************************/
+
+struct alloc_chain{
+ void *ptr;
+ struct alloc_chain *next;
+};
+
+/* vorbis_info contains all the setup information specific to the
+ specific compression/decompression mode in progress (eg,
+ psychoacoustic settings, channel setup, options, codebook
+ etc). vorbis_info and substructures are in backends.h.
+*********************************************************************/
+
+/* the comments are not part of vorbis_info so that vorbis_info can be
+ static storage */
+typedef struct vorbis_comment{
+ /* unlimited user comment fields. libvorbis writes 'libvorbis'
+ whatever vendor is set to in encode */
+ char **user_comments;
+ int *comment_lengths;
+ int comments;
+ char *vendor;
+
+} vorbis_comment;
+
+
+/* libvorbis encodes in two abstraction layers; first we perform DSP
+ and produce a packet (see docs/analysis.txt). The packet is then
+ coded into a framed OggSquish bitstream by the second layer (see
+ docs/framing.txt). Decode is the reverse process; we sync/frame
+ the bitstream and extract individual packets, then decode the
+ packet back into PCM audio.
+
+ The extra framing/packetizing is used in streaming formats, such as
+ files. Over the net (such as with UDP), the framing and
+ packetization aren't necessary as they're provided by the transport
+ and the streaming layer is not used */
+
+/* Vorbis PRIMITIVES: general ***************************************/
+
+static void (*qvorbis_info_init) (vorbis_info *vi);
+static void (*qvorbis_info_clear) (vorbis_info *vi);
+static void (*qvorbis_comment_init) (vorbis_comment *vc);
+static void (*qvorbis_comment_clear) (vorbis_comment *vc);
+
+static int (*qvorbis_block_init) (vorbis_dsp_state *v, vorbis_block *vb);
+static int (*qvorbis_block_clear) (vorbis_block *vb);
+static void (*qvorbis_dsp_clear) (vorbis_dsp_state *v);
+static double (*qvorbis_granule_time) (vorbis_dsp_state *v,
+ ogg_int64_t granulepos);
+
+/* Vorbis PRIMITIVES: analysis/DSP layer ****************************/
+
+static int (*qvorbis_analysis_init) (vorbis_dsp_state *v,vorbis_info *vi);
+static int (*qvorbis_commentheader_out) (vorbis_comment *vc, ogg_packet *op);
+static int (*qvorbis_analysis_headerout) (vorbis_dsp_state *v,
+ vorbis_comment *vc,
+ ogg_packet *op,
+ ogg_packet *op_comm,
+ ogg_packet *op_code);
+static float ** (*qvorbis_analysis_buffer) (vorbis_dsp_state *v,int vals);
+static int (*qvorbis_analysis_wrote) (vorbis_dsp_state *v,int vals);
+static int (*qvorbis_analysis_blockout) (vorbis_dsp_state *v,vorbis_block *vb);
+static int (*qvorbis_analysis) (vorbis_block *vb,ogg_packet *op);
+
+static int (*qvorbis_bitrate_addblock) (vorbis_block *vb);
+static int (*qvorbis_bitrate_flushpacket) (vorbis_dsp_state *vd,
+ ogg_packet *op);
+
+// end of vorbis/codec.h stuff
+
+// vorbisenc.h stuff
+static int (*qvorbis_encode_init_vbr) (vorbis_info *vi,
+ long channels,
+ long rate,
+
+ float base_quality /* quality level from 0. (lo) to 1. (hi) */
+ );
+// end of vorbisenc.h stuff
+
+// theora.h stuff
+
+#define TH_ENCCTL_SET_VP3_COMPATIBLE (10)
+
+typedef struct {
+ int y_width; /**< Width of the Y' luminance plane */
+ int y_height; /**< Height of the luminance plane */
+ int y_stride; /**< Offset in bytes between successive rows */
+
+ int uv_width; /**< Width of the Cb and Cr chroma planes */
+ int uv_height; /**< Height of the chroma planes */
+ int uv_stride; /**< Offset between successive chroma rows */
+ unsigned char *y; /**< Pointer to start of luminance data */
+ unsigned char *u; /**< Pointer to start of Cb data */
+ unsigned char *v; /**< Pointer to start of Cr data */
+
+} yuv_buffer;
+
+/**
+ * A Colorspace.
+ */
+typedef enum {
+ OC_CS_UNSPECIFIED, /**< The colorspace is unknown or unspecified */
+ OC_CS_ITU_REC_470M, /**< This is the best option for 'NTSC' content */
+ OC_CS_ITU_REC_470BG, /**< This is the best option for 'PAL' content */
+ OC_CS_NSPACES /**< This marks the end of the defined colorspaces */
+} theora_colorspace;
+
+/**
+ * A Chroma subsampling
+ *
+ * These enumerate the available chroma subsampling options supported
+ * by the theora format. See Section 4.4 of the specification for
+ * exact definitions.
+ */
+typedef enum {
+ OC_PF_420, /**< Chroma subsampling by 2 in each direction (4:2:0) */
+ OC_PF_RSVD, /**< Reserved value */
+ OC_PF_422, /**< Horizonatal chroma subsampling by 2 (4:2:2) */
+ OC_PF_444 /**< No chroma subsampling at all (4:4:4) */
+} theora_pixelformat;
+/**
+ * Theora bitstream info.
+ * Contains the basic playback parameters for a stream,
+ * corresponding to the initial 'info' header packet.
+ *
+ * Encoded theora frames must be a multiple of 16 in width and height.
+ * To handle other frame sizes, a crop rectangle is specified in
+ * frame_height and frame_width, offset_x and * offset_y. The offset
+ * and size should still be a multiple of 2 to avoid chroma sampling
+ * shifts. Offset values in this structure are measured from the
+ * upper left of the image.
+ *
+ * Frame rate, in frames per second, is stored as a rational
+ * fraction. Aspect ratio is also stored as a rational fraction, and
+ * refers to the aspect ratio of the frame pixels, not of the
+ * overall frame itself.
+ *
+ * See
+ * examples/encoder_example.c for usage examples of the
+ * other paramters and good default settings for the encoder parameters.
+ */
+typedef struct {
+ ogg_uint32_t width; /**< encoded frame width */
+ ogg_uint32_t height; /**< encoded frame height */
+ ogg_uint32_t frame_width; /**< display frame width */
+ ogg_uint32_t frame_height; /**< display frame height */
+ ogg_uint32_t offset_x; /**< horizontal offset of the displayed frame */
+ ogg_uint32_t offset_y; /**< vertical offset of the displayed frame */
+ ogg_uint32_t fps_numerator; /**< frame rate numerator **/
+ ogg_uint32_t fps_denominator; /**< frame rate denominator **/
+ ogg_uint32_t aspect_numerator; /**< pixel aspect ratio numerator */
+ ogg_uint32_t aspect_denominator; /**< pixel aspect ratio denominator */
+ theora_colorspace colorspace; /**< colorspace */
+ int target_bitrate; /**< nominal bitrate in bits per second */
+ int quality; /**< Nominal quality setting, 0-63 */
+ int quick_p; /**< Quick encode/decode */
+
+ /* decode only */
+ unsigned char version_major;
+ unsigned char version_minor;
+ unsigned char version_subminor;
+
+ void *codec_setup;
+
+ /* encode only */
+ int dropframes_p;
+ int keyframe_auto_p;
+ ogg_uint32_t keyframe_frequency;
+ ogg_uint32_t keyframe_frequency_force; /* also used for decode init to
+ get granpos shift correct */
+ ogg_uint32_t keyframe_data_target_bitrate;
+ ogg_int32_t keyframe_auto_threshold;
+ ogg_uint32_t keyframe_mindistance;
+ ogg_int32_t noise_sensitivity;
+ ogg_int32_t sharpness;
+
+ theora_pixelformat pixelformat; /**< chroma subsampling mode to expect */
+
+} theora_info;
+
+/** Codec internal state and context.
+ */
+typedef struct{
+ theora_info *i;
+ ogg_int64_t granulepos;
+
+ void *internal_encode;
+ void *internal_decode;
+
+} theora_state;
+
+/**
+ * Comment header metadata.
+ *
+ * This structure holds the in-stream metadata corresponding to
+ * the 'comment' header packet.
+ *
+ * Meta data is stored as a series of (tag, value) pairs, in
+ * length-encoded string vectors. The first occurence of the
+ * '=' character delimits the tag and value. A particular tag
+ * may occur more than once. The character set encoding for
+ * the strings is always UTF-8, but the tag names are limited
+ * to case-insensitive ASCII. See the spec for details.
+ *
+ * In filling in this structure, qtheora_decode_header() will
+ * null-terminate the user_comment strings for safety. However,
+ * the bitstream format itself treats them as 8-bit clean,
+ * and so the length array should be treated as authoritative
+ * for their length.
+ */
+typedef struct theora_comment{
+ char **user_comments; /**< An array of comment string vectors */
+ int *comment_lengths; /**< An array of corresponding string vector lengths in bytes */
+ int comments; /**< The total number of comment string vectors */
+ char *vendor; /**< The vendor string identifying the encoder, null terminated */
+
+} theora_comment;
+static int (*qtheora_encode_init) (theora_state *th, theora_info *ti);
+static int (*qtheora_encode_YUVin) (theora_state *t, yuv_buffer *yuv);
+static int (*qtheora_encode_packetout) ( theora_state *t, int last_p,
+ ogg_packet *op);
+static int (*qtheora_encode_header) (theora_state *t, ogg_packet *op);
+static int (*qtheora_encode_comment) (theora_comment *tc, ogg_packet *op);
+static int (*qtheora_encode_tables) (theora_state *t, ogg_packet *op);
+static void (*qtheora_info_init) (theora_info *c);
+static void (*qtheora_info_clear) (theora_info *c);
+static void (*qtheora_clear) (theora_state *t);
+static void (*qtheora_comment_init) (theora_comment *tc);
+static void (*qtheora_comment_clear) (theora_comment *tc);
+static double (*qtheora_granule_time) (theora_state *th,ogg_int64_t granulepos);
+static int (*qtheora_control) (theora_state *th,int req,void *buf,size_t buf_sz);
+// end of theora.h stuff
+
+static dllfunction_t oggfuncs[] =
+{
+ {"ogg_stream_packetin", (void **) &qogg_stream_packetin},
+ {"ogg_stream_pageout", (void **) &qogg_stream_pageout},
+ {"ogg_stream_flush", (void **) &qogg_stream_flush},
+ {"ogg_stream_init", (void **) &qogg_stream_init},
+ {"ogg_stream_clear", (void **) &qogg_stream_clear},
+ {"ogg_page_granulepos", (void **) &qogg_page_granulepos},
+ {NULL, NULL}
+};
+
+static dllfunction_t vorbisencfuncs[] =
+{
+ {"vorbis_encode_init_vbr", (void **) &qvorbis_encode_init_vbr},
+ {NULL, NULL}
+};
+
+static dllfunction_t vorbisfuncs[] =
+{
+ {"vorbis_info_init", (void **) &qvorbis_info_init},
+ {"vorbis_info_clear", (void **) &qvorbis_info_clear},
+ {"vorbis_comment_init", (void **) &qvorbis_comment_init},
+ {"vorbis_comment_clear", (void **) &qvorbis_comment_clear},
+ {"vorbis_block_init", (void **) &qvorbis_block_init},
+ {"vorbis_block_clear", (void **) &qvorbis_block_clear},
+ {"vorbis_dsp_clear", (void **) &qvorbis_dsp_clear},
+ {"vorbis_analysis_init", (void **) &qvorbis_analysis_init},
+ {"vorbis_commentheader_out", (void **) &qvorbis_commentheader_out},
+ {"vorbis_analysis_headerout", (void **) &qvorbis_analysis_headerout},
+ {"vorbis_analysis_buffer", (void **) &qvorbis_analysis_buffer},
+ {"vorbis_analysis_wrote", (void **) &qvorbis_analysis_wrote},
+ {"vorbis_analysis_blockout", (void **) &qvorbis_analysis_blockout},
+ {"vorbis_analysis", (void **) &qvorbis_analysis},
+ {"vorbis_bitrate_addblock", (void **) &qvorbis_bitrate_addblock},
+ {"vorbis_bitrate_flushpacket", (void **) &qvorbis_bitrate_flushpacket},
+ {"vorbis_granule_time", (void **) &qvorbis_granule_time},
+ {NULL, NULL}
+};
+
+static dllfunction_t theorafuncs[] =
+{
+ {"theora_info_init", (void **) &qtheora_info_init},
+ {"theora_info_clear", (void **) &qtheora_info_clear},
+ {"theora_comment_init", (void **) &qtheora_comment_init},
+ {"theora_comment_clear", (void **) &qtheora_comment_clear},
+ {"theora_encode_init", (void **) &qtheora_encode_init},
+ {"theora_encode_YUVin", (void **) &qtheora_encode_YUVin},
+ {"theora_encode_packetout", (void **) &qtheora_encode_packetout},
+ {"theora_encode_header", (void **) &qtheora_encode_header},
+ {"theora_encode_comment", (void **) &qtheora_encode_comment},
+ {"theora_encode_tables", (void **) &qtheora_encode_tables},
+ {"theora_clear", (void **) &qtheora_clear},
+ {"theora_granule_time", (void **) &qtheora_granule_time},
+ {"theora_control", (void **) &qtheora_control},
+ {NULL, NULL}
+};
+
+static dllhandle_t og_dll = NULL, vo_dll = NULL, ve_dll = NULL, th_dll = NULL;
+
+static qboolean SCR_CaptureVideo_Ogg_OpenLibrary(void)
+{
+ const char* dllnames_og [] =
+ {
+#if defined(WIN32)
+ "libogg-0.dll",
+ "libogg.dll",
+ "ogg.dll",
+#elif defined(MACOSX)
+ "libogg.dylib",
+#else
+ "libogg.so.0",
+ "libogg.so",
+#endif
+ NULL
+ };
+ const char* dllnames_vo [] =
+ {
+#if defined(WIN32)
+ "libvorbis-0.dll",
+ "libvorbis.dll",
+ "vorbis.dll",
+#elif defined(MACOSX)
+ "libvorbis.dylib",
+#else
+ "libvorbis.so.0",
+ "libvorbis.so",
+#endif
+ NULL
+ };
+ const char* dllnames_ve [] =
+ {
+#if defined(WIN32)
+ "libvorbisenc-2.dll",
+ "libvorbisenc.dll",
+ "vorbisenc.dll",
+#elif defined(MACOSX)
+ "libvorbisenc.dylib",
+#else
+ "libvorbisenc.so.2",
+ "libvorbisenc.so",
+#endif
+ NULL
+ };
+ const char* dllnames_th [] =
+ {
+#if defined(WIN32)
+ "libtheora-0.dll",
+ "libtheora.dll",
+ "theora.dll",
+#elif defined(MACOSX)
+ "libtheora.dylib",
+#else
+ "libtheora.so.0",
+ "libtheora.so",
+#endif
+ NULL
+ };
+
+ return
+ Sys_LoadLibrary (dllnames_og, &og_dll, oggfuncs)
+ &&
+ Sys_LoadLibrary (dllnames_th, &th_dll, theorafuncs)
+ &&
+ Sys_LoadLibrary (dllnames_vo, &vo_dll, vorbisfuncs)
+ &&
+ Sys_LoadLibrary (dllnames_ve, &ve_dll, vorbisencfuncs);
+}
+
+void SCR_CaptureVideo_Ogg_Init(void)
+{
+ SCR_CaptureVideo_Ogg_OpenLibrary();
+
+ Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_vp3compat);
+ Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_quality);
+ Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_bitrate);
+ Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier);
+ Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_maxinterval);
+ Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_mininterval);
+ Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_keyframe_auto_threshold);
+ Cvar_RegisterVariable(&cl_capturevideo_ogg_theora_noise_sensitivity);
+ Cvar_RegisterVariable(&cl_capturevideo_ogg_vorbis_quality);
+}
+
+qboolean SCR_CaptureVideo_Ogg_Available(void)
+{
+ return og_dll && th_dll && vo_dll && ve_dll;
+}
+
+void SCR_CaptureVideo_Ogg_CloseDLL(void)
+{
+ Sys_UnloadLibrary (&ve_dll);
+ Sys_UnloadLibrary (&vo_dll);
+ Sys_UnloadLibrary (&th_dll);
+ Sys_UnloadLibrary (&og_dll);
+}
+
+// this struct should not be needed
+// however, libogg appears to pull the ogg_page's data element away from our
+// feet before we get to write the data due to interleaving
+// so this struct is used to keep the page data around until it actually gets
+// written
+typedef struct allocatedoggpage_s
+{
+ size_t len;
+ double time;
+ unsigned char data[65307];
+ // this number is from RFC 3533. In case libogg writes more, we'll have to increase this
+ // but we'll get a Host_Error in this case so we can track it down
+}
+allocatedoggpage_t;
+
+typedef struct capturevideostate_ogg_formatspecific_s
+{
+ ogg_stream_state to, vo;
+ int serial1, serial2;
+ theora_state ts;
+ vorbis_dsp_state vd;
+ vorbis_block vb;
+ vorbis_info vi;
+ yuv_buffer yuv[2];
+ int yuvi;
+ int lastnum;
+ int channels;
+
+ allocatedoggpage_t videopage, audiopage;
+}
+capturevideostate_ogg_formatspecific_t;
+#define LOAD_FORMATSPECIFIC_OGG() capturevideostate_ogg_formatspecific_t *format = (capturevideostate_ogg_formatspecific_t *) cls.capturevideo.formatspecific
+
+static void SCR_CaptureVideo_Ogg_Interleave(void)
+{
+ LOAD_FORMATSPECIFIC_OGG();
+ ogg_page pg;
+
+ if(!cls.capturevideo.soundrate)
+ {
+ while(qogg_stream_pageout(&format->to, &pg) > 0)
+ {
+ FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len);
+ FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len);
+ }
+ return;
+ }
+
+ for(;;)
+ {
+ // first: make sure we have a page of both types
+ if(!format->videopage.len)
+ if(qogg_stream_pageout(&format->to, &pg) > 0)
+ {
+ format->videopage.len = pg.header_len + pg.body_len;
+ format->videopage.time = qtheora_granule_time(&format->ts, qogg_page_granulepos(&pg));
+ if(format->videopage.len > sizeof(format->videopage.data))
+ Sys_Error("video page too long");
+ memcpy(format->videopage.data, pg.header, pg.header_len);
+ memcpy(format->videopage.data + pg.header_len, pg.body, pg.body_len);
+ }
+ if(!format->audiopage.len)
+ if(qogg_stream_pageout(&format->vo, &pg) > 0)
+ {
+ format->audiopage.len = pg.header_len + pg.body_len;
+ format->audiopage.time = qvorbis_granule_time(&format->vd, qogg_page_granulepos(&pg));
+ if(format->audiopage.len > sizeof(format->audiopage.data))
+ Sys_Error("audio page too long");
+ memcpy(format->audiopage.data, pg.header, pg.header_len);
+ memcpy(format->audiopage.data + pg.header_len, pg.body, pg.body_len);
+ }
+
+ if(format->videopage.len && format->audiopage.len)
+ {
+ // output the page that ends first
+ if(format->videopage.time < format->audiopage.time)
+ {
+ FS_Write(cls.capturevideo.videofile, format->videopage.data, format->videopage.len);
+ format->videopage.len = 0;
+ }
+ else
+ {
+ FS_Write(cls.capturevideo.videofile, format->audiopage.data, format->audiopage.len);
+ format->audiopage.len = 0;
+ }
+ }
+ else
+ break;
+ }
+}
+
+static void SCR_CaptureVideo_Ogg_FlushInterleaving(void)
+{
+ LOAD_FORMATSPECIFIC_OGG();
+
+ if(cls.capturevideo.soundrate)
+ if(format->audiopage.len)
+ {
+ FS_Write(cls.capturevideo.videofile, format->audiopage.data, format->audiopage.len);
+ format->audiopage.len = 0;
+ }
+
+ if(format->videopage.len)
+ {
+ FS_Write(cls.capturevideo.videofile, format->videopage.data, format->videopage.len);
+ format->videopage.len = 0;
+ }
+}
+
+static void SCR_CaptureVideo_Ogg_EndVideo(void)
+{
+ LOAD_FORMATSPECIFIC_OGG();
+ ogg_page pg;
+ ogg_packet pt;
+
+ if(format->yuvi >= 0)
+ {
+ // send the previous (and last) frame
+ while(format->lastnum-- > 0)
+ {
+ qtheora_encode_YUVin(&format->ts, &format->yuv[format->yuvi]);
+
+ while(qtheora_encode_packetout(&format->ts, !format->lastnum, &pt))
+ qogg_stream_packetin(&format->to, &pt);
+
+ SCR_CaptureVideo_Ogg_Interleave();
+ }
+ }
+
+ if(cls.capturevideo.soundrate)
+ {
+ qvorbis_analysis_wrote(&format->vd, 0);
+ while(qvorbis_analysis_blockout(&format->vd, &format->vb) == 1)
+ {
+ qvorbis_analysis(&format->vb, NULL);
+ qvorbis_bitrate_addblock(&format->vb);
+ while(qvorbis_bitrate_flushpacket(&format->vd, &pt))
+ qogg_stream_packetin(&format->vo, &pt);
+ SCR_CaptureVideo_Ogg_Interleave();
+ }
+ }
+
+ SCR_CaptureVideo_Ogg_FlushInterleaving();
+
+ while(qogg_stream_pageout(&format->to, &pg) > 0)
+ {
+ FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len);
+ FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len);
+ }
+
+ if(cls.capturevideo.soundrate)
+ {
+ while(qogg_stream_pageout(&format->vo, &pg) > 0)
+ {
+ FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len);
+ FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len);
+ }
+ }
+
+ while (1) {
+ int result = qogg_stream_flush (&format->to, &pg);
+ if (result < 0)
+ fprintf (stderr, "Internal Ogg library error.\n"); // TODO Sys_Error
+ if (result <= 0)
+ break;
+ FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len);
+ FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len);
+ }
+
+ if(cls.capturevideo.soundrate)
+ {
+ while (1) {
+ int result = qogg_stream_flush (&format->vo, &pg);
+ if (result < 0)
+ fprintf (stderr, "Internal Ogg library error.\n"); // TODO Sys_Error
+ if (result <= 0)
+ break;
+ FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len);
+ FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len);
+ }
+
+ qogg_stream_clear(&format->vo);
+ qvorbis_block_clear(&format->vb);
+ qvorbis_dsp_clear(&format->vd);
+ }
+
+ qogg_stream_clear(&format->to);
+ qtheora_clear(&format->ts);
+ qvorbis_info_clear(&format->vi);
+
+ Mem_Free(format->yuv[0].y);
+ Mem_Free(format->yuv[0].u);
+ Mem_Free(format->yuv[0].v);
+ Mem_Free(format->yuv[1].y);
+ Mem_Free(format->yuv[1].u);
+ Mem_Free(format->yuv[1].v);
+ Mem_Free(format);
+
+ FS_Close(cls.capturevideo.videofile);
+ cls.capturevideo.videofile = NULL;
+}
+
+static void SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV(void)
+{
+ LOAD_FORMATSPECIFIC_OGG();
+ yuv_buffer *yuv;
+ int x, y;
+ int blockr, blockg, blockb;
+ unsigned char *b;
+ int w = cls.capturevideo.width;
+ int h = cls.capturevideo.height;
+ int inpitch = w*4;
+
+ yuv = &format->yuv[format->yuvi];
+
+ for(y = 0; y < h; ++y)
+ {
+ for(b = cls.capturevideo.outbuffer + (h-1-y)*w*4, x = 0; x < w; ++x)
+ {
+ blockr = b[2];
+ blockg = b[1];
+ blockb = b[0];
+ yuv->y[x + yuv->y_stride * y] =
+ cls.capturevideo.yuvnormalizetable[0][cls.capturevideo.rgbtoyuvscaletable[0][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[0][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[0][2][blockb]];
+ b += 4;
+ }
+
+ if ((y & 1) == 0 && y/2 < h/2) // if h is odd, this skips the last row
+ {
+ for(b = cls.capturevideo.outbuffer + (h-2-y)*w*4, x = 0; x < w/2; ++x)
+ {
+ blockr = (b[2] + b[6] + b[inpitch+2] + b[inpitch+6]) >> 2;
+ blockg = (b[1] + b[5] + b[inpitch+1] + b[inpitch+5]) >> 2;
+ blockb = (b[0] + b[4] + b[inpitch+0] + b[inpitch+4]) >> 2;
+ yuv->u[x + yuv->uv_stride * (y/2)] =
+ cls.capturevideo.yuvnormalizetable[1][cls.capturevideo.rgbtoyuvscaletable[1][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[1][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[1][2][blockb] + 128];
+ yuv->v[x + yuv->uv_stride * (y/2)] =
+ cls.capturevideo.yuvnormalizetable[2][cls.capturevideo.rgbtoyuvscaletable[2][0][blockr] + cls.capturevideo.rgbtoyuvscaletable[2][1][blockg] + cls.capturevideo.rgbtoyuvscaletable[2][2][blockb] + 128];
+ b += 8;
+ }
+ }
+ }
+}
+
+static void SCR_CaptureVideo_Ogg_VideoFrames(int num)
+{
+ LOAD_FORMATSPECIFIC_OGG();
+ ogg_packet pt;
+
+ // data is in cls.capturevideo.outbuffer as BGRA and has size width*height
+
+ if(format->yuvi >= 0)
+ {
+ // send the previous frame
+ while(format->lastnum-- > 0)
+ {
+ qtheora_encode_YUVin(&format->ts, &format->yuv[format->yuvi]);
+
+ while(qtheora_encode_packetout(&format->ts, false, &pt))
+ qogg_stream_packetin(&format->to, &pt);
+
+ SCR_CaptureVideo_Ogg_Interleave();
+ }
+ }
+
+ format->yuvi = (format->yuvi + 1) % 2;
+ SCR_CaptureVideo_Ogg_ConvertFrame_BGRA_to_YUV();
+ format->lastnum = num;
+
+ // TODO maybe send num-1 frames from here already
+}
+
+typedef int channelmapping_t[8];
+channelmapping_t mapping[8] =
+{
+ { 0, -1, -1, -1, -1, -1, -1, -1 }, // mono
+ { 0, 1, -1, -1, -1, -1, -1, -1 }, // stereo
+ { 0, 1, 2, -1, -1, -1, -1, -1 }, // L C R
+ { 0, 1, 2, 3, -1, -1, -1, -1 }, // surround40
+ { 0, 2, 3, 4, 1, -1, -1, -1 }, // FL FC FR RL RR
+ { 0, 2, 3, 4, 1, 5, -1, -1 }, // surround51
+ { 0, 2, 3, 4, 1, 5, 6, -1 }, // (not defined by vorbis spec)
+ { 0, 2, 3, 4, 1, 5, 6, 7 } // surround71 (not defined by vorbis spec)
+};
+
+static void SCR_CaptureVideo_Ogg_SoundFrame(const portable_sampleframe_t *paintbuffer, size_t length)
+{
+ LOAD_FORMATSPECIFIC_OGG();
+ float **vorbis_buffer;
+ size_t i;
+ int j;
+ ogg_packet pt;
+ int *map = mapping[bound(1, cls.capturevideo.soundchannels, 8) - 1];
+
+ vorbis_buffer = qvorbis_analysis_buffer(&format->vd, length);
+ for(j = 0; j < cls.capturevideo.soundchannels; ++j)
+ {
+ float *b = vorbis_buffer[map[j]];
+ for(i = 0; i < length; ++i)
+ b[i] = paintbuffer[i].sample[j];
+ }
+ qvorbis_analysis_wrote(&format->vd, length);
+
+ while(qvorbis_analysis_blockout(&format->vd, &format->vb) == 1)
+ {
+ qvorbis_analysis(&format->vb, NULL);
+ qvorbis_bitrate_addblock(&format->vb);
+
+ while(qvorbis_bitrate_flushpacket(&format->vd, &pt))
+ qogg_stream_packetin(&format->vo, &pt);
+ }
+
+ SCR_CaptureVideo_Ogg_Interleave();
+}
+
+void SCR_CaptureVideo_Ogg_BeginVideo(void)
+{
+ char vabuf[1024];
+ cls.capturevideo.format = CAPTUREVIDEOFORMAT_OGG_VORBIS_THEORA;
+ cls.capturevideo.formatextension = "ogv";
+ cls.capturevideo.videofile = FS_OpenRealFile(va(vabuf, sizeof(vabuf), "%s.%s", cls.capturevideo.basename, cls.capturevideo.formatextension), "wb", false);
+ cls.capturevideo.endvideo = SCR_CaptureVideo_Ogg_EndVideo;
+ cls.capturevideo.videoframes = SCR_CaptureVideo_Ogg_VideoFrames;
+ cls.capturevideo.soundframe = SCR_CaptureVideo_Ogg_SoundFrame;
+ cls.capturevideo.formatspecific = Mem_Alloc(tempmempool, sizeof(capturevideostate_ogg_formatspecific_t));
+ {
+ LOAD_FORMATSPECIFIC_OGG();
+ int num, denom, i;
+ ogg_page pg;
+ ogg_packet pt, pt2, pt3;
+ theora_comment tc;
+ vorbis_comment vc;
+ theora_info ti;
+ int vp3compat;
+
+ format->serial1 = rand();
+ qogg_stream_init(&format->to, format->serial1);
+
+ if(cls.capturevideo.soundrate)
+ {
+ do
+ {
+ format->serial2 = rand();
+ }
+ while(format->serial1 == format->serial2);
+ qogg_stream_init(&format->vo, format->serial2);
+ }
+
+ format->videopage.len = format->audiopage.len = 0;
+
+ qtheora_info_init(&ti);
+ ti.frame_width = cls.capturevideo.width;
+ ti.frame_height = cls.capturevideo.height;
+ ti.width = (ti.frame_width + 15) & ~15;
+ ti.height = (ti.frame_height + 15) & ~15;
+ //ti.offset_x = ((ti.width - ti.frame_width) / 2) & ~1;
+ //ti.offset_y = ((ti.height - ti.frame_height) / 2) & ~1;
+
+ for(i = 0; i < 2; ++i)
+ {
+ format->yuv[i].y_width = ti.width;
+ format->yuv[i].y_height = ti.height;
+ format->yuv[i].y_stride = ti.width;
+ format->yuv[i].uv_width = ti.width / 2;
+ format->yuv[i].uv_height = ti.height / 2;
+ format->yuv[i].uv_stride = ti.width / 2;
+ format->yuv[i].y = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].y_stride * format->yuv[i].y_height);
+ format->yuv[i].u = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].uv_stride * format->yuv[i].uv_height);
+ format->yuv[i].v = (unsigned char *) Mem_Alloc(tempmempool, format->yuv[i].uv_stride * format->yuv[i].uv_height);
+ }
+ format->yuvi = -1; // -1: no frame valid yet, write into 0
+
+ FindFraction(cls.capturevideo.framerate / cls.capturevideo.framestep, &num, &denom, 1001);
+ ti.fps_numerator = num;
+ ti.fps_denominator = denom;
+
+ FindFraction(1 / vid_pixelheight.value, &num, &denom, 1000);
+ ti.aspect_numerator = num;
+ ti.aspect_denominator = denom;
+
+ ti.colorspace = OC_CS_UNSPECIFIED;
+ ti.pixelformat = OC_PF_420;
+
+ ti.quick_p = true; // http://mlblog.osdir.com/multimedia.ogg.theora.general/2004-07/index.shtml
+ ti.dropframes_p = false;
+
+ ti.target_bitrate = cl_capturevideo_ogg_theora_bitrate.integer * 1000;
+ ti.quality = cl_capturevideo_ogg_theora_quality.integer;
+
+ if(ti.target_bitrate <= 0)
+ {
+ ti.target_bitrate = -1;
+ ti.keyframe_data_target_bitrate = (unsigned int)-1;
+ }
+ else
+ {
+ ti.keyframe_data_target_bitrate = (int) (ti.target_bitrate * max(1, cl_capturevideo_ogg_theora_keyframe_bitrate_multiplier.value));
+
+ if(ti.target_bitrate < 45000 || ti.target_bitrate > 2000000)
+ Con_DPrintf("WARNING: requesting an odd bitrate for theora (sensible values range from 45 to 2000 kbps)\n");
+ }
+
+ if(ti.quality < 0 || ti.quality > 63)
+ {
+ ti.quality = 63;
+ if(ti.target_bitrate <= 0)
+ {
+ ti.target_bitrate = 0x7FFFFFFF;
+ ti.keyframe_data_target_bitrate = 0x7FFFFFFF;
+ }
+ }
+
+ // this -1 magic is because ti.keyframe_frequency and ti.keyframe_mindistance use different metrics
+ ti.keyframe_frequency = bound(1, cl_capturevideo_ogg_theora_keyframe_maxinterval.integer, 1000);
+ ti.keyframe_mindistance = bound(1, cl_capturevideo_ogg_theora_keyframe_mininterval.integer, (int) ti.keyframe_frequency) - 1;
+ ti.noise_sensitivity = bound(0, cl_capturevideo_ogg_theora_noise_sensitivity.integer, 6);
+ ti.sharpness = bound(0, cl_capturevideo_ogg_theora_sharpness.integer, 2);
+ ti.keyframe_auto_threshold = bound(0, cl_capturevideo_ogg_theora_keyframe_auto_threshold.integer, 100);
+
+ ti.keyframe_frequency_force = ti.keyframe_frequency;
+ ti.keyframe_auto_p = (ti.keyframe_frequency != ti.keyframe_mindistance + 1);
+
+ qtheora_encode_init(&format->ts, &ti);
+ qtheora_info_clear(&ti);
+
+ if(cl_capturevideo_ogg_theora_vp3compat.integer)
+ {
+ vp3compat = 1;
+ qtheora_control(&format->ts, TH_ENCCTL_SET_VP3_COMPATIBLE, &vp3compat, sizeof(vp3compat));
+ if(!vp3compat)
+ Con_DPrintf("Warning: theora stream is not fully VP3 compatible\n");
+ }
+
+ // vorbis?
+ if(cls.capturevideo.soundrate)
+ {
+ qvorbis_info_init(&format->vi);
+ qvorbis_encode_init_vbr(&format->vi, cls.capturevideo.soundchannels, cls.capturevideo.soundrate, bound(-1, cl_capturevideo_ogg_vorbis_quality.value, 10) * 0.099);
+ qvorbis_comment_init(&vc);
+ qvorbis_analysis_init(&format->vd, &format->vi);
+ qvorbis_block_init(&format->vd, &format->vb);
+ }
+
+ qtheora_comment_init(&tc);
+
+ /* create the remaining theora headers */
+ qtheora_encode_header(&format->ts, &pt);
+ qogg_stream_packetin(&format->to, &pt);
+ if (qogg_stream_pageout (&format->to, &pg) != 1)
+ fprintf (stderr, "Internal Ogg library error.\n");
+ FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len);
+ FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len);
+
+ qtheora_encode_comment(&tc, &pt);
+ qogg_stream_packetin(&format->to, &pt);
+ qtheora_encode_tables(&format->ts, &pt);
+ qogg_stream_packetin (&format->to, &pt);
+
+ qtheora_comment_clear(&tc);
+
+ if(cls.capturevideo.soundrate)
+ {
+ qvorbis_analysis_headerout(&format->vd, &vc, &pt, &pt2, &pt3);
+ qogg_stream_packetin(&format->vo, &pt);
+ if (qogg_stream_pageout (&format->vo, &pg) != 1)
+ fprintf (stderr, "Internal Ogg library error.\n");
+ FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len);
+ FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len);
+
+ qogg_stream_packetin(&format->vo, &pt2);
+ qogg_stream_packetin(&format->vo, &pt3);
+
+ qvorbis_comment_clear(&vc);
+ }
+
+ for(;;)
+ {
+ int result = qogg_stream_flush (&format->to, &pg);
+ if (result < 0)
+ fprintf (stderr, "Internal Ogg library error.\n"); // TODO Sys_Error
+ if (result <= 0)
+ break;
+ FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len);
+ FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len);
+ }
+
+ if(cls.capturevideo.soundrate)
+ for(;;)
+ {
+ int result = qogg_stream_flush (&format->vo, &pg);
+ if (result < 0)
+ fprintf (stderr, "Internal Ogg library error.\n"); // TODO Sys_Error
+ if (result <= 0)
+ break;
+ FS_Write(cls.capturevideo.videofile, pg.header, pg.header_len);
+ FS_Write(cls.capturevideo.videofile, pg.body, pg.body_len);
+ }
+ }
+}
diff --git a/app/jni/cap_ogg.h b/app/jni/cap_ogg.h
new file mode 100644
index 0000000..81718f2
--- /dev/null
+++ b/app/jni/cap_ogg.h
@@ -0,0 +1,4 @@
+void SCR_CaptureVideo_Ogg_Init(void);
+qboolean SCR_CaptureVideo_Ogg_Available(void);
+void SCR_CaptureVideo_Ogg_BeginVideo(void);
+void SCR_CaptureVideo_Ogg_CloseDLL(void);
diff --git a/app/jni/cd_null.c b/app/jni/cd_null.c
new file mode 100644
index 0000000..13826f0
--- /dev/null
+++ b/app/jni/cd_null.c
@@ -0,0 +1,91 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+#include "quakedef.h"
+#include "cdaudio.h"
+
+
+void CDAudio_SysEject (void)
+{
+}
+
+
+void CDAudio_SysCloseDoor (void)
+{
+}
+
+
+int CDAudio_SysGetAudioDiskInfo (void)
+{
+ return -1;
+}
+
+
+float CDAudio_SysGetVolume (void)
+{
+ return -1.0f;
+}
+
+
+void CDAudio_SysSetVolume (float volume)
+{
+}
+
+
+int CDAudio_SysPlay (int track)
+{
+ return -1;
+}
+
+
+int CDAudio_SysStop (void)
+{
+ return -1;
+}
+
+
+int CDAudio_SysPause (void)
+{
+ return -1;
+}
+
+int CDAudio_SysResume (void)
+{
+ return -1;
+}
+
+int CDAudio_SysUpdate (void)
+{
+ return -1;
+}
+
+
+void CDAudio_SysInit (void)
+{
+}
+
+int CDAudio_SysStartup (void)
+{
+ return -1;
+}
+
+void CDAudio_SysShutdown (void)
+{
+}
diff --git a/app/jni/cd_shared.c b/app/jni/cd_shared.c
new file mode 100644
index 0000000..43b4a7f
--- /dev/null
+++ b/app/jni/cd_shared.c
@@ -0,0 +1,783 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All
+// rights reserved.
+
+#include "quakedef.h"
+#include "cdaudio.h"
+#include "sound.h"
+
+// used by menu to ghost CD audio slider
+cvar_t cdaudioinitialized = {CVAR_READONLY,"cdaudioinitialized","0","indicates if CD Audio system is active"};
+cvar_t cdaudio = {CVAR_SAVE,"cdaudio","1","CD playing mode (0 = never access CD drive, 1 = play CD tracks if no replacement available, 2 = play fake tracks if no CD track available, 3 = play only real CD tracks, 4 = play real CD tracks even instead of named fake tracks)"};
+
+#define MAX_PLAYLISTS 10
+int music_playlist_active = -1;
+int music_playlist_playing = 0; // 0 = not playing, 1 = playing, -1 = tried and failed
+
+cvar_t music_playlist_index = {0, "music_playlist_index", "-1", "selects which of the music_playlist_ variables is the active one, -1 disables playlists"};
+cvar_t music_playlist_list[MAX_PLAYLISTS] =
+{
+ {0, "music_playlist_list0", "", "list of tracks to play"},
+ {0, "music_playlist_list1", "", "list of tracks to play"},
+ {0, "music_playlist_list2", "", "list of tracks to play"},
+ {0, "music_playlist_list3", "", "list of tracks to play"},
+ {0, "music_playlist_list4", "", "list of tracks to play"},
+ {0, "music_playlist_list5", "", "list of tracks to play"},
+ {0, "music_playlist_list6", "", "list of tracks to play"},
+ {0, "music_playlist_list7", "", "list of tracks to play"},
+ {0, "music_playlist_list8", "", "list of tracks to play"},
+ {0, "music_playlist_list9", "", "list of tracks to play"}
+};
+cvar_t music_playlist_current[MAX_PLAYLISTS] =
+{
+ {0, "music_playlist_current0", "0", "current track index to play in list"},
+ {0, "music_playlist_current1", "0", "current track index to play in list"},
+ {0, "music_playlist_current2", "0", "current track index to play in list"},
+ {0, "music_playlist_current3", "0", "current track index to play in list"},
+ {0, "music_playlist_current4", "0", "current track index to play in list"},
+ {0, "music_playlist_current5", "0", "current track index to play in list"},
+ {0, "music_playlist_current6", "0", "current track index to play in list"},
+ {0, "music_playlist_current7", "0", "current track index to play in list"},
+ {0, "music_playlist_current8", "0", "current track index to play in list"},
+ {0, "music_playlist_current9", "0", "current track index to play in list"},
+};
+cvar_t music_playlist_random[MAX_PLAYLISTS] =
+{
+ {0, "music_playlist_random0", "0", "enables random play order if 1, 0 is sequential play"},
+ {0, "music_playlist_random1", "0", "enables random play order if 1, 0 is sequential play"},
+ {0, "music_playlist_random2", "0", "enables random play order if 1, 0 is sequential play"},
+ {0, "music_playlist_random3", "0", "enables random play order if 1, 0 is sequential play"},
+ {0, "music_playlist_random4", "0", "enables random play order if 1, 0 is sequential play"},
+ {0, "music_playlist_random5", "0", "enables random play order if 1, 0 is sequential play"},
+ {0, "music_playlist_random6", "0", "enables random play order if 1, 0 is sequential play"},
+ {0, "music_playlist_random7", "0", "enables random play order if 1, 0 is sequential play"},
+ {0, "music_playlist_random8", "0", "enables random play order if 1, 0 is sequential play"},
+ {0, "music_playlist_random9", "0", "enables random play order if 1, 0 is sequential play"},
+};
+cvar_t music_playlist_sampleposition[MAX_PLAYLISTS] =
+{
+ {0, "music_playlist_sampleposition0", "-1", "resume position for track, -1 restarts every time"},
+ {0, "music_playlist_sampleposition1", "-1", "resume position for track, -1 restarts every time"},
+ {0, "music_playlist_sampleposition2", "-1", "resume position for track, -1 restarts every time"},
+ {0, "music_playlist_sampleposition3", "-1", "resume position for track, -1 restarts every time"},
+ {0, "music_playlist_sampleposition4", "-1", "resume position for track, -1 restarts every time"},
+ {0, "music_playlist_sampleposition5", "-1", "resume position for track, -1 restarts every time"},
+ {0, "music_playlist_sampleposition6", "-1", "resume position for track, -1 restarts every time"},
+ {0, "music_playlist_sampleposition7", "-1", "resume position for track, -1 restarts every time"},
+ {0, "music_playlist_sampleposition8", "-1", "resume position for track, -1 restarts every time"},
+ {0, "music_playlist_sampleposition9", "-1", "resume position for track, -1 restarts every time"},
+};
+
+static qboolean wasPlaying = false;
+static qboolean initialized = false;
+static qboolean enabled = false;
+static float cdvolume;
+typedef char filename_t[MAX_QPATH];
+#ifdef MAXTRACKS
+static filename_t remap[MAXTRACKS];
+#endif
+static unsigned char maxTrack;
+static int faketrack = -1;
+
+static float saved_vol = 1.0f;
+
+// exported variables
+qboolean cdValid = false;
+qboolean cdPlaying = false;
+qboolean cdPlayLooping = false;
+unsigned char cdPlayTrack;
+
+cl_cdstate_t cd;
+
+static void CDAudio_Eject (void)
+{
+ if (!enabled)
+ return;
+
+ if(cdaudio.integer == 0)
+ return;
+
+ CDAudio_SysEject();
+}
+
+
+static void CDAudio_CloseDoor (void)
+{
+ if (!enabled)
+ return;
+
+ if(cdaudio.integer == 0)
+ return;
+
+ CDAudio_SysCloseDoor();
+}
+
+static int CDAudio_GetAudioDiskInfo (void)
+{
+ int ret;
+
+ cdValid = false;
+
+ if(cdaudio.integer == 0)
+ return -1;
+
+ ret = CDAudio_SysGetAudioDiskInfo();
+ if (ret < 1)
+ return -1;
+
+ cdValid = true;
+ maxTrack = ret;
+
+ return 0;
+}
+
+static qboolean CDAudio_Play_real (int track, qboolean looping, qboolean complain)
+{
+ if(track < 1)
+ {
+ if(complain)
+ Con_Print("Could not load BGM track.\n");
+ return false;
+ }
+
+ if (!cdValid)
+ {
+ CDAudio_GetAudioDiskInfo();
+ if (!cdValid)
+ {
+ if(complain)
+ Con_DPrint ("No CD in player.\n");
+ return false;
+ }
+ }
+
+ if (track > maxTrack)
+ {
+ if(complain)
+ Con_DPrintf("CDAudio: Bad track number %u.\n", track);
+ return false;
+ }
+
+ if (CDAudio_SysPlay(track) == -1)
+ return false;
+
+ if(cdaudio.integer != 3)
+ Con_DPrintf ("CD track %u playing...\n", track);
+
+ return true;
+}
+
+void CDAudio_Play_byName (const char *trackname, qboolean looping, qboolean tryreal, float startposition)
+{
+ unsigned int track;
+ sfx_t* sfx;
+ char filename[MAX_QPATH];
+
+ Host_StartVideo();
+
+ if (!enabled)
+ return;
+
+ if(tryreal && strspn(trackname, "0123456789") == strlen(trackname))
+ {
+ track = (unsigned char) atoi(trackname);
+#ifdef MAXTRACKS
+ if(track > 0 && track < MAXTRACKS)
+ if(*remap[track])
+ {
+ if(strspn(remap[track], "0123456789") == strlen(remap[track]))
+ {
+ trackname = remap[track];
+ }
+ else
+ {
+ // ignore remappings to fake tracks if we're going to play a real track
+ switch(cdaudio.integer)
+ {
+ case 0: // we never access CD
+ case 1: // we have a replacement
+ trackname = remap[track];
+ break;
+ case 2: // we only use fake track replacement if CD track is invalid
+ CDAudio_GetAudioDiskInfo();
+ if(!cdValid || track > maxTrack)
+ trackname = remap[track];
+ break;
+ case 3: // we always play from CD - ignore this remapping then
+ case 4: // we randomize anyway
+ break;
+ }
+ }
+ }
+#endif
+ }
+
+ if(tryreal && strspn(trackname, "0123456789") == strlen(trackname))
+ {
+ track = (unsigned char) atoi(trackname);
+ if (track < 1)
+ {
+ Con_DPrintf("CDAudio: Bad track number %u.\n", track);
+ return;
+ }
+ }
+ else
+ track = 0;
+
+ // div0: I assume this code was intentionally there. Maybe turn it into a cvar?
+ if (cdPlaying && cdPlayTrack == track && faketrack == -1)
+ return;
+ CDAudio_Stop ();
+
+ if(track >= 1)
+ {
+ if(cdaudio.integer == 3) // only play real CD tracks at all
+ {
+ if(CDAudio_Play_real(track, looping, true))
+ goto success;
+ return;
+ }
+
+ if(cdaudio.integer == 2) // prefer real CD track over fake
+ {
+ if(CDAudio_Play_real(track, looping, false))
+ goto success;
+ }
+ }
+
+ if(cdaudio.integer == 4) // only play real CD tracks, EVEN instead of fake tracks!
+ {
+ if(CDAudio_Play_real(track, looping, false))
+ goto success;
+
+ if(cdValid && maxTrack > 0)
+ {
+ track = 1 + (rand() % maxTrack);
+ if(CDAudio_Play_real(track, looping, true))
+ goto success;
+ }
+ else
+ {
+ Con_DPrint ("No CD in player.\n");
+ }
+ return;
+ }
+
+ // Try playing a fake track (sound file) first
+ if(track >= 1)
+ {
+ dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%03u.wav", track);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%03u.ogg", track);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/track%03u.ogg", track);// added by motorsep
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/track%03u.ogg", track);// added by motorsep
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%02u.wav", track);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/track%02u.ogg", track);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/track%02u.ogg", track);// added by motorsep
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/track%02u.ogg", track);// added by motorsep
+ }
+ else
+ {
+ dpsnprintf(filename, sizeof(filename), "%s", trackname);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "%s.wav", trackname);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "%s.ogg", trackname);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s", trackname);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s.wav", trackname);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/%s.ogg", trackname);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s", trackname);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s.wav", trackname);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "sound/cdtracks/%s.ogg", trackname);
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/%s.ogg", trackname); // added by motorsep
+ if (!FS_FileExists(filename)) dpsnprintf(filename, sizeof(filename), "music/cdtracks/%s.ogg", trackname); // added by motorsep
+ }
+ if (FS_FileExists(filename) && (sfx = S_PrecacheSound (filename, false, false)))
+ {
+ faketrack = S_StartSound_StartPosition_Flags (-1, 0, sfx, vec3_origin, cdvolume, 0, startposition, (looping ? CHANNELFLAG_FORCELOOP : 0) | CHANNELFLAG_FULLVOLUME | CHANNELFLAG_LOCALSOUND, 1.0f);
+ if (faketrack != -1)
+ {
+ if(track >= 1)
+ {
+ if(cdaudio.integer != 0) // we don't need these messages if only fake tracks can be played anyway
+ Con_DPrintf ("Fake CD track %u playing...\n", track);
+ }
+ else
+ Con_DPrintf ("BGM track %s playing...\n", trackname);
+ }
+ }
+
+ // If we can't play a fake CD track, try the real one
+ if (faketrack == -1)
+ {
+ if(cdaudio.integer == 0 || track < 1)
+ {
+ Con_Print("Could not load BGM track.\n");
+ return;
+ }
+ else
+ {
+ if(!CDAudio_Play_real(track, looping, true))
+ return;
+ }
+ }
+
+success:
+ cdPlayLooping = looping;
+ cdPlayTrack = track;
+ cdPlaying = true;
+
+ if (cdvolume == 0.0 || bgmvolume.value == 0)
+ CDAudio_Pause ();
+}
+
+void CDAudio_Play (int track, qboolean looping)
+{
+ char buf[20];
+ if (music_playlist_index.integer >= 0)
+ return;
+ dpsnprintf(buf, sizeof(buf), "%d", (int) track);
+ CDAudio_Play_byName(buf, looping, true, 0);
+}
+
+float CDAudio_GetPosition (void)
+{
+ if(faketrack != -1)
+ return S_GetChannelPosition(faketrack);
+ return -1;
+}
+
+static void CDAudio_StopPlaylistTrack(void);
+
+void CDAudio_Stop (void)
+{
+ if (!enabled)
+ return;
+
+ // save the playlist position
+ CDAudio_StopPlaylistTrack();
+
+ if (faketrack != -1)
+ {
+ S_StopChannel (faketrack, true, true);
+ faketrack = -1;
+ }
+ else if (cdPlaying && (CDAudio_SysStop() == -1))
+ return;
+ else if(wasPlaying)
+ {
+ CDAudio_Resume(); // needed by SDL - can't stop while paused there (causing pause/stop to fail after play, pause, stop, play otherwise)
+ if (cdPlaying && (CDAudio_SysStop() == -1))
+ return;
+ }
+
+ wasPlaying = false;
+ cdPlaying = false;
+}
+
+void CDAudio_Pause (void)
+{
+ if (!enabled || !cdPlaying)
+ return;
+
+ if (faketrack != -1)
+ S_SetChannelFlag (faketrack, CHANNELFLAG_PAUSED, true);
+ else if (CDAudio_SysPause() == -1)
+ return;
+
+ wasPlaying = cdPlaying;
+ cdPlaying = false;
+}
+
+
+void CDAudio_Resume (void)
+{
+ if (!enabled || cdPlaying || !wasPlaying)
+ return;
+
+ if (faketrack != -1)
+ S_SetChannelFlag (faketrack, CHANNELFLAG_PAUSED, false);
+ else if (CDAudio_SysResume() == -1)
+ return;
+ cdPlaying = true;
+}
+
+static void CD_f (void)
+{
+ const char *command;
+#ifdef MAXTRACKS
+ int ret;
+ int n;
+#endif
+
+ command = Cmd_Argv (1);
+
+ if (strcasecmp(command, "remap") != 0)
+ Host_StartVideo();
+
+ if (strcasecmp(command, "on") == 0)
+ {
+ enabled = true;
+ return;
+ }
+
+ if (strcasecmp(command, "off") == 0)
+ {
+ CDAudio_Stop();
+ enabled = false;
+ return;
+ }
+
+ if (strcasecmp(command, "reset") == 0)
+ {
+ enabled = true;
+ CDAudio_Stop();
+#ifdef MAXTRACKS
+ for (n = 0; n < MAXTRACKS; n++)
+ *remap[n] = 0; // empty string, that is, unremapped
+#endif
+ CDAudio_GetAudioDiskInfo();
+ return;
+ }
+
+ if (strcasecmp(command, "rescan") == 0)
+ {
+ CDAudio_Shutdown();
+ CDAudio_Startup();
+ return;
+ }
+
+ if (strcasecmp(command, "remap") == 0)
+ {
+#ifdef MAXTRACKS
+ ret = Cmd_Argc() - 2;
+ if (ret <= 0)
+ {
+ for (n = 1; n < MAXTRACKS; n++)
+ if (*remap[n])
+ Con_Printf(" %u -> %s\n", n, remap[n]);
+ return;
+ }
+ for (n = 1; n <= ret; n++)
+ strlcpy(remap[n], Cmd_Argv (n+1), sizeof(*remap));
+#endif
+ return;
+ }
+
+ if (strcasecmp(command, "close") == 0)
+ {
+ CDAudio_CloseDoor();
+ return;
+ }
+
+ if (strcasecmp(command, "play") == 0)
+ {
+ if (music_playlist_index.integer >= 0)
+ return;
+ CDAudio_Play_byName(Cmd_Argv (2), false, true, (Cmd_Argc() > 3) ? atof( Cmd_Argv(3) ) : 0);
+ return;
+ }
+
+ if (strcasecmp(command, "loop") == 0)
+ {
+ if (music_playlist_index.integer >= 0)
+ return;
+ CDAudio_Play_byName(Cmd_Argv (2), true, true, (Cmd_Argc() > 3) ? atof( Cmd_Argv(3) ) : 0);
+ return;
+ }
+
+ if (strcasecmp(command, "stop") == 0)
+ {
+ if (music_playlist_index.integer >= 0)
+ return;
+ CDAudio_Stop();
+ return;
+ }
+
+ if (strcasecmp(command, "pause") == 0)
+ {
+ if (music_playlist_index.integer >= 0)
+ return;
+ CDAudio_Pause();
+ return;
+ }
+
+ if (strcasecmp(command, "resume") == 0)
+ {
+ if (music_playlist_index.integer >= 0)
+ return;
+ CDAudio_Resume();
+ return;
+ }
+
+ if (strcasecmp(command, "eject") == 0)
+ {
+ if (faketrack == -1)
+ CDAudio_Stop();
+ CDAudio_Eject();
+ cdValid = false;
+ return;
+ }
+
+ if (strcasecmp(command, "info") == 0)
+ {
+ CDAudio_GetAudioDiskInfo ();
+ if (cdValid)
+ Con_Printf("%u tracks on CD.\n", maxTrack);
+ else
+ Con_Print ("No CD in player.\n");
+ if (cdPlaying)
+ Con_Printf("Currently %s track %u\n", cdPlayLooping ? "looping" : "playing", cdPlayTrack);
+ else if (wasPlaying)
+ Con_Printf("Paused %s track %u\n", cdPlayLooping ? "looping" : "playing", cdPlayTrack);
+ if (cdvolume >= 0)
+ Con_Printf("Volume is %f\n", cdvolume);
+ else
+ Con_Printf("Can't get CD volume\n");
+ return;
+ }
+
+ Con_Printf("CD commands:\n");
+ Con_Printf("cd on - enables CD audio system\n");
+ Con_Printf("cd off - stops and disables CD audio system\n");
+ Con_Printf("cd reset - resets CD audio system (clears track remapping and re-reads disc information)\n");
+ Con_Printf("cd rescan - rescans disks in drives (to use another disc)\n");
+ Con_Printf("cd remap [remap2] [remap3] [...] - chooses (possibly emulated) CD tracks to play when a map asks for a particular track, this has many uses\n");
+ Con_Printf("cd close - closes CD tray\n");
+ Con_Printf("cd eject - stops playing music and opens CD tray to allow you to change disc\n");
+ Con_Printf("cd play - plays selected track in remapping table\n");
+ Con_Printf("cd loop - plays and repeats selected track in remapping table\n");
+ Con_Printf("cd stop - stops playing current CD track\n");
+ Con_Printf("cd pause - pauses CD playback\n");
+ Con_Printf("cd resume - unpauses CD playback\n");
+ Con_Printf("cd info - prints basic disc information (number of tracks, currently playing track, volume level)\n");
+}
+
+static void CDAudio_SetVolume (float newvol)
+{
+ // If the volume hasn't changed
+ if (newvol == cdvolume)
+ return;
+
+ // If the CD has been muted
+ if (newvol == 0.0f)
+ CDAudio_Pause ();
+ else
+ {
+ // If the CD has been unmuted
+ if (cdvolume == 0.0f)
+ CDAudio_Resume ();
+
+ if (faketrack != -1)
+ S_SetChannelVolume (faketrack, newvol);
+ else
+ CDAudio_SysSetVolume (newvol * mastervolume.value);
+ }
+
+ cdvolume = newvol;
+}
+
+static void CDAudio_StopPlaylistTrack(void)
+{
+ if (music_playlist_active >= 0 && music_playlist_active < MAX_PLAYLISTS && music_playlist_sampleposition[music_playlist_active].value >= 0)
+ {
+ // save position for resume
+ float position = CDAudio_GetPosition();
+ Cvar_SetValueQuick(&music_playlist_sampleposition[music_playlist_active], position >= 0 ? position : 0);
+ }
+ music_playlist_active = -1;
+ music_playlist_playing = 0; // not playing
+}
+
+void CDAudio_StartPlaylist(qboolean resume)
+{
+ const char *list;
+ const char *t;
+ int index;
+ int current;
+ int randomplay;
+ int count;
+ int listindex;
+ float position;
+ char trackname[MAX_QPATH];
+ CDAudio_Stop();
+ index = music_playlist_index.integer;
+ if (index >= 0 && index < MAX_PLAYLISTS && bgmvolume.value > 0)
+ {
+ list = music_playlist_list[index].string;
+ current = music_playlist_current[index].integer;
+ randomplay = music_playlist_random[index].integer;
+ position = music_playlist_sampleposition[index].value;
+ count = 0;
+ trackname[0] = 0;
+ if (list && list[0])
+ {
+ for (t = list;;count++)
+ {
+ if (!COM_ParseToken_Console(&t))
+ break;
+ // if we don't find the desired track, use the first one
+ if (count == 0)
+ strlcpy(trackname, com_token, sizeof(trackname));
+ }
+ }
+ if (count > 0)
+ {
+ // position < 0 means never resume track
+ if (position < 0)
+ position = 0;
+ // advance to next track in playlist if the last one ended
+ if (!resume)
+ {
+ position = 0;
+ current++;
+ if (randomplay)
+ current = (int)lhrandom(0, count);
+ }
+ // wrap playlist position if needed
+ if (current >= count)
+ current = 0;
+ // set current
+ Cvar_SetValueQuick(&music_playlist_current[index], current);
+ // get the Nth trackname
+ if (current >= 0 && current < count)
+ {
+ for (listindex = 0, t = list;;listindex++)
+ {
+ if (!COM_ParseToken_Console(&t))
+ break;
+ if (listindex == current)
+ {
+ strlcpy(trackname, com_token, sizeof(trackname));
+ break;
+ }
+ }
+ }
+ if (trackname[0])
+ {
+ CDAudio_Play_byName(trackname, false, false, position);
+ if (faketrack != -1)
+ music_playlist_active = index;
+ }
+ }
+ }
+ music_playlist_playing = music_playlist_active >= 0 ? 1 : -1;
+}
+
+void CDAudio_Update (void)
+{
+ static int lastplaylist = -1;
+ if (!enabled)
+ return;
+
+ CDAudio_SetVolume (bgmvolume.value);
+ if (music_playlist_playing > 0 && CDAudio_GetPosition() < 0)
+ {
+ // this track ended, start a new track from the beginning
+ CDAudio_StartPlaylist(false);
+ lastplaylist = music_playlist_index.integer;
+ }
+ else if (lastplaylist != music_playlist_index.integer
+ || (bgmvolume.value > 0 && !music_playlist_playing && music_playlist_index.integer >= 0))
+ {
+ // active playlist changed, save position and switch track
+ CDAudio_StartPlaylist(true);
+ lastplaylist = music_playlist_index.integer;
+ }
+
+ if (faketrack == -1 && cdaudio.integer != 0 && bgmvolume.value != 0)
+ CDAudio_SysUpdate();
+}
+
+int CDAudio_Init (void)
+{
+ int i;
+
+ if (cls.state == ca_dedicated)
+ return -1;
+
+// COMMANDLINEOPTION: Sound: -nocdaudio disables CD audio support
+ if (COM_CheckParm("-nocdaudio"))
+ return -1;
+
+ CDAudio_SysInit();
+
+#ifdef MAXTRACKS
+ for (i = 0; i < MAXTRACKS; i++)
+ *remap[i] = 0;
+#endif
+
+ Cvar_RegisterVariable(&cdaudio);
+ Cvar_RegisterVariable(&cdaudioinitialized);
+ Cvar_SetValueQuick(&cdaudioinitialized, true);
+ enabled = true;
+
+ Cvar_RegisterVariable(&music_playlist_index);
+ for (i = 0;i < MAX_PLAYLISTS;i++)
+ {
+ Cvar_RegisterVariable(&music_playlist_list[i]);
+ Cvar_RegisterVariable(&music_playlist_current[i]);
+ Cvar_RegisterVariable(&music_playlist_random[i]);
+ Cvar_RegisterVariable(&music_playlist_sampleposition[i]);
+ }
+
+ Cmd_AddCommand("cd", CD_f, "execute a CD drive command (cd on/off/reset/remap/close/play/loop/stop/pause/resume/eject/info) - use cd by itself for usage");
+
+ return 0;
+}
+
+int CDAudio_Startup (void)
+{
+ if (COM_CheckParm("-nocdaudio"))
+ return -1;
+
+ CDAudio_SysStartup ();
+
+ if (CDAudio_GetAudioDiskInfo())
+ {
+ Con_Print("CDAudio_Init: No CD in player.\n");
+ cdValid = false;
+ }
+
+ saved_vol = CDAudio_SysGetVolume ();
+ if (saved_vol < 0.0f)
+ {
+ Con_Print ("Can't get initial CD volume\n");
+ saved_vol = 1.0f;
+ }
+ else
+ Con_Printf ("Initial CD volume: %g\n", saved_vol);
+
+ initialized = true;
+
+ Con_Print("CD Audio Initialized\n");
+
+ return 0;
+}
+
+void CDAudio_Shutdown (void)
+{
+ if (!initialized)
+ return;
+
+ CDAudio_SysSetVolume (saved_vol);
+
+ CDAudio_Stop();
+ CDAudio_SysShutdown();
+ initialized = false;
+}
diff --git a/app/jni/cdaudio.h b/app/jni/cdaudio.h
new file mode 100644
index 0000000..5eac430
--- /dev/null
+++ b/app/jni/cdaudio.h
@@ -0,0 +1,66 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+typedef struct cl_cdstate_s
+{
+ qboolean Valid;
+ qboolean Playing;
+ qboolean PlayLooping;
+ unsigned char PlayTrack;
+}
+cl_cdstate_t;
+
+//extern cl_cdstate_t cd;
+
+extern qboolean cdValid;
+extern qboolean cdPlaying;
+extern qboolean cdPlayLooping;
+extern unsigned char cdPlayTrack;
+
+extern cvar_t cdaudioinitialized;
+
+int CDAudio_Init(void);
+void CDAudio_Open(void);
+void CDAudio_Close(void);
+void CDAudio_Play(int track, qboolean looping);
+void CDAudio_Play_byName (const char *trackname, qboolean looping, qboolean tryreal, float startposition);
+void CDAudio_Stop(void);
+void CDAudio_Pause(void);
+void CDAudio_Resume(void);
+int CDAudio_Startup(void);
+void CDAudio_Shutdown(void);
+void CDAudio_Update(void);
+float CDAudio_GetPosition(void);
+void CDAudio_StartPlaylist(qboolean resume);
+
+// Prototypes of the system dependent functions
+void CDAudio_SysEject (void);
+void CDAudio_SysCloseDoor (void);
+int CDAudio_SysGetAudioDiskInfo (void);
+float CDAudio_SysGetVolume (void);
+void CDAudio_SysSetVolume (float volume);
+int CDAudio_SysPlay (int track);
+int CDAudio_SysStop (void);
+int CDAudio_SysPause (void);
+int CDAudio_SysResume (void);
+int CDAudio_SysUpdate (void);
+void CDAudio_SysInit (void);
+int CDAudio_SysStartup (void);
+void CDAudio_SysShutdown (void);
diff --git a/app/jni/cflags.mk b/app/jni/cflags.mk
new file mode 100644
index 0000000..2ea077b
--- /dev/null
+++ b/app/jni/cflags.mk
@@ -0,0 +1,30 @@
+# This file is included in all .mk files to ensure their compilation flags are in sync
+# across debug and release builds.
+
+# NOTE: this is not part of import_vrlib.mk because VRLib itself needs to have these flags
+# set, but VRLib's make file cannot include import_vrlib.mk or it would be importing itself.
+
+LOCAL_CFLAGS := -DANDROID_NDK
+LOCAL_CFLAGS += -Werror # error on warnings
+LOCAL_CFLAGS += -Wall
+LOCAL_CFLAGS += -Wextra
+#LOCAL_CFLAGS += -Wlogical-op # not part of -Wall or -Wextra
+#LOCAL_CFLAGS += -Weffc++ # too many issues to fix for now
+LOCAL_CFLAGS += -Wno-strict-aliasing # TODO: need to rewrite some code
+LOCAL_CFLAGS += -Wno-unused-parameter
+LOCAL_CFLAGS += -Wno-missing-field-initializers # warns on this: SwipeAction ret = {}
+LOCAL_CFLAGS += -Wno-multichar # used in internal Android headers: DISPLAY_EVENT_VSYNC = 'vsyn',
+LOCAL_CPPFLAGS := -Wno-type-limits
+LOCAL_CPPFLAGS += -Wno-invalid-offsetof
+
+# disable deprecation errors, but keep the warnings
+LOCAL_CFLAGS += -Wno-error=deprecated-declarations
+
+ifeq ($(OVR_DEBUG),1)
+ LOCAL_CFLAGS += -DOVR_BUILD_DEBUG=1 -O0 -g
+else
+ LOCAL_CFLAGS += -O3
+endif
+
+# Explicitly compile for the ARM and not the Thumb instruction set.
+LOCAL_ARM_MODE := arm
diff --git a/app/jni/cl_collision.c b/app/jni/cl_collision.c
new file mode 100644
index 0000000..f0e0f7d
--- /dev/null
+++ b/app/jni/cl_collision.c
@@ -0,0 +1,1079 @@
+
+#include "quakedef.h"
+#include "cl_collision.h"
+
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+float CL_SelectTraceLine(const vec3_t start, const vec3_t pEnd, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent)
+#else
+float CL_SelectTraceLine(const vec3_t start, const vec3_t end, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent)
+#endif
+{
+ float maxfrac, maxrealfrac;
+ int n;
+ entity_render_t *ent;
+ vec_t tracemins[3], tracemaxs[3];
+ trace_t trace;
+ vec_t tempnormal[3], starttransformed[3], endtransformed[3];
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+ vec3_t end;
+ vec_t len = 0;
+
+ if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0)
+ {
+ // TRICK: make the trace 1 qu longer!
+ VectorSubtract(pEnd, start, end);
+ len = VectorNormalizeLength(end);
+ VectorMA(pEnd, collision_endposnudge.value, end, end);
+ }
+ else
+ VectorCopy(pEnd, end);
+#endif
+
+ memset (&trace, 0 , sizeof(trace_t));
+ trace.fraction = 1;
+ trace.realfraction = 1;
+ VectorCopy (end, trace.endpos);
+
+ if (hitent)
+ *hitent = 0;
+ if (cl.worldmodel && cl.worldmodel->TraceLine)
+ cl.worldmodel->TraceLine(cl.worldmodel, NULL, NULL, &trace, start, end, SUPERCONTENTS_SOLID);
+
+ if (normal)
+ VectorCopy(trace.plane.normal, normal);
+ maxfrac = trace.fraction;
+ maxrealfrac = trace.realfraction;
+
+ tracemins[0] = min(start[0], end[0]);
+ tracemaxs[0] = max(start[0], end[0]);
+ tracemins[1] = min(start[1], end[1]);
+ tracemaxs[1] = max(start[1], end[1]);
+ tracemins[2] = min(start[2], end[2]);
+ tracemaxs[2] = max(start[2], end[2]);
+
+ // look for embedded bmodels
+ for (n = 0;n < cl.num_entities;n++)
+ {
+ if (!cl.entities_active[n])
+ continue;
+ ent = &cl.entities[n].render;
+ if (!BoxesOverlap(ent->mins, ent->maxs, tracemins, tracemaxs))
+ continue;
+ if (!ent->model || !ent->model->TraceLine)
+ continue;
+ if ((ent->flags & RENDER_EXTERIORMODEL) && !chase_active.integer)
+ continue;
+ // if transparent and not selectable, skip entity
+ if (!(cl.entities[n].state_current.effects & EF_SELECTABLE) && (ent->alpha < 1 || (ent->effects & (EF_ADDITIVE | EF_NODEPTHTEST))))
+ continue;
+ if (ent == ignoreent)
+ continue;
+ Matrix4x4_Transform(&ent->inversematrix, start, starttransformed);
+ Matrix4x4_Transform(&ent->inversematrix, end, endtransformed);
+ Collision_ClipTrace_Box(&trace, ent->model->normalmins, ent->model->normalmaxs, starttransformed, vec3_origin, vec3_origin, endtransformed, SUPERCONTENTS_SOLID, SUPERCONTENTS_SOLID, 0, NULL);
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+ if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0)
+ Collision_ShortenTrace(&trace, len / (len + collision_endposnudge.value), pEnd);
+#endif
+ if (maxrealfrac < trace.realfraction)
+ continue;
+
+ ent->model->TraceLine(ent->model, ent->frameblend, ent->skeleton, &trace, starttransformed, endtransformed, SUPERCONTENTS_SOLID);
+
+ if (maxrealfrac > trace.realfraction)
+ {
+ if (hitent)
+ *hitent = n;
+ maxfrac = trace.fraction;
+ maxrealfrac = trace.realfraction;
+ if (normal)
+ {
+ VectorCopy(trace.plane.normal, tempnormal);
+ Matrix4x4_Transform3x3(&ent->matrix, tempnormal, normal);
+ }
+ }
+ }
+ maxfrac = bound(0, maxfrac, 1);
+ //maxrealfrac = bound(0, maxrealfrac, 1);
+ //if (maxfrac < 0 || maxfrac > 1) Con_Printf("fraction out of bounds %f %s:%d\n", maxfrac, __FILE__, __LINE__);
+ if (impact)
+ VectorLerp(start, maxfrac, end, impact);
+ return maxfrac;
+}
+
+void CL_FindNonSolidLocation(const vec3_t in, vec3_t out, vec_t radius)
+{
+ // FIXME: check multiple brush models
+ if (cl.worldmodel && cl.worldmodel->brush.FindNonSolidLocation)
+ cl.worldmodel->brush.FindNonSolidLocation(cl.worldmodel, in, out, radius);
+}
+
+dp_model_t *CL_GetModelByIndex(int modelindex)
+{
+ if(!modelindex)
+ return NULL;
+ if (modelindex < 0)
+ {
+ modelindex = -(modelindex+1);
+ if (modelindex < MAX_MODELS)
+ return cl.csqc_model_precache[modelindex];
+ }
+ else
+ {
+ if(modelindex < MAX_MODELS)
+ return cl.model_precache[modelindex];
+ }
+ return NULL;
+}
+
+dp_model_t *CL_GetModelFromEdict(prvm_edict_t *ed)
+{
+ prvm_prog_t *prog = CLVM_prog;
+ if (!ed || ed->priv.server->free)
+ return NULL;
+ return CL_GetModelByIndex((int)PRVM_clientedictfloat(ed, modelindex));
+}
+
+void CL_LinkEdict(prvm_edict_t *ent)
+{
+ prvm_prog_t *prog = CLVM_prog;
+ vec3_t mins, maxs;
+
+ if (ent == prog->edicts)
+ return; // don't add the world
+
+ if (ent->priv.server->free)
+ return;
+
+ // set the abs box
+
+ if (PRVM_clientedictfloat(ent, solid) == SOLID_BSP)
+ {
+ dp_model_t *model = CL_GetModelByIndex( (int)PRVM_clientedictfloat(ent, modelindex) );
+ if (model == NULL)
+ {
+ Con_Printf("edict %i: SOLID_BSP with invalid modelindex!\n", PRVM_NUM_FOR_EDICT(ent));
+
+ model = CL_GetModelByIndex( 0 );
+ }
+
+ if( model != NULL )
+ {
+ if (!model->TraceBox)
+ Con_DPrintf("edict %i: SOLID_BSP with non-collidable model\n", PRVM_NUM_FOR_EDICT(ent));
+
+ if (PRVM_clientedictvector(ent, angles)[0] || PRVM_clientedictvector(ent, angles)[2] || PRVM_clientedictvector(ent, avelocity)[0] || PRVM_clientedictvector(ent, avelocity)[2])
+ {
+ VectorAdd(PRVM_clientedictvector(ent, origin), model->rotatedmins, mins);
+ VectorAdd(PRVM_clientedictvector(ent, origin), model->rotatedmaxs, maxs);
+ }
+ else if (PRVM_clientedictvector(ent, angles)[1] || PRVM_clientedictvector(ent, avelocity)[1])
+ {
+ VectorAdd(PRVM_clientedictvector(ent, origin), model->yawmins, mins);
+ VectorAdd(PRVM_clientedictvector(ent, origin), model->yawmaxs, maxs);
+ }
+ else
+ {
+ VectorAdd(PRVM_clientedictvector(ent, origin), model->normalmins, mins);
+ VectorAdd(PRVM_clientedictvector(ent, origin), model->normalmaxs, maxs);
+ }
+ }
+ else
+ {
+ // SOLID_BSP with no model is valid, mainly because some QC setup code does so temporarily
+ VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins);
+ VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs);
+ }
+ }
+ else
+ {
+ VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, mins), mins);
+ VectorAdd(PRVM_clientedictvector(ent, origin), PRVM_clientedictvector(ent, maxs), maxs);
+ }
+
+ VectorCopy(mins, PRVM_clientedictvector(ent, absmin));
+ VectorCopy(maxs, PRVM_clientedictvector(ent, absmax));
+
+ World_LinkEdict(&cl.world, ent, mins, maxs);
+}
+
+int CL_GenericHitSuperContentsMask(const prvm_edict_t *passedict)
+{
+ prvm_prog_t *prog = CLVM_prog;
+ if (passedict)
+ {
+ int dphitcontentsmask = (int)PRVM_clientedictfloat(passedict, dphitcontentsmask);
+ if (dphitcontentsmask)
+ return dphitcontentsmask;
+ else if (PRVM_clientedictfloat(passedict, solid) == SOLID_SLIDEBOX)
+ {
+ if ((int)PRVM_clientedictfloat(passedict, flags) & FL_MONSTER)
+ return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_MONSTERCLIP;
+ else
+ return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_PLAYERCLIP;
+ }
+ else if (PRVM_clientedictfloat(passedict, solid) == SOLID_CORPSE)
+ return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY;
+ else if (PRVM_clientedictfloat(passedict, solid) == SOLID_TRIGGER)
+ return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY;
+ else
+ return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE;
+ }
+ else
+ return SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY | SUPERCONTENTS_CORPSE;
+}
+
+/*
+==================
+CL_Move
+==================
+*/
+trace_t CL_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities)
+{
+ prvm_prog_t *prog = CLVM_prog;
+ int i, bodysupercontents;
+ int passedictprog;
+ prvm_edict_t *traceowner, *touch;
+ trace_t trace;
+ // temporary storage because prvm_vec_t may need conversion
+ vec3_t touchmins, touchmaxs;
+ // bounding box of entire move area
+ vec3_t clipboxmins, clipboxmaxs;
+ // size when clipping against monsters
+ vec3_t clipmins2, clipmaxs2;
+ // start and end origin of move
+ vec3_t clipstart;
+ // trace results
+ trace_t cliptrace;
+ // matrices to transform into/out of other entity's space
+ matrix4x4_t matrix, imatrix;
+ // model of other entity
+ dp_model_t *model;
+ // list of entities to test for collisions
+ int numtouchedicts;
+ static prvm_edict_t *touchedicts[MAX_EDICTS];
+
+ if (hitnetworkentity)
+ *hitnetworkentity = 0;
+
+ VectorCopy(start, clipstart);
+ VectorClear(clipmins2);
+ VectorClear(clipmaxs2);
+#if COLLISIONPARANOID >= 3
+ Con_Printf("move(%f %f %f)", clipstart[0], clipstart[1], clipstart[2]);
+#endif
+
+ // clip to world
+ Collision_ClipPointToWorld(&cliptrace, cl.worldmodel, clipstart, hitsupercontentsmask);
+ cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid;
+ if (cliptrace.startsolid || cliptrace.fraction < 1)
+ cliptrace.ent = prog ? prog->edicts : NULL;
+ if (type == MOVE_WORLDONLY)
+ goto finished;
+
+ if (type == MOVE_MISSILE)
+ {
+ // LordHavoc: modified this, was = -15, now -= 15
+ for (i = 0;i < 3;i++)
+ {
+ clipmins2[i] -= 15;
+ clipmaxs2[i] += 15;
+ }
+ }
+
+ // create the bounding box of the entire move
+ for (i = 0;i < 3;i++)
+ {
+ clipboxmins[i] = clipstart[i] - 1;
+ clipboxmaxs[i] = clipstart[i] + 1;
+ }
+
+ // debug override to test against everything
+ if (sv_debugmove.integer)
+ {
+ clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999;
+ clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999;
+ }
+
+ // if the passedict is world, make it NULL (to avoid two checks each time)
+ // this checks prog because this function is often called without a CSQC
+ // VM context
+ if (prog == NULL || passedict == prog->edicts)
+ passedict = NULL;
+ // precalculate prog value for passedict for comparisons
+ passedictprog = prog != NULL ? PRVM_EDICT_TO_PROG(passedict) : 0;
+ // precalculate passedict's owner edict pointer for comparisons
+ traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_clientedictedict(passedict, owner)) : NULL;
+
+ // collide against network entities
+ if (hitnetworkbrushmodels)
+ {
+ for (i = 0;i < cl.num_brushmodel_entities;i++)
+ {
+ entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render;
+ if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs))
+ continue;
+ Collision_ClipPointToGenericEntity(&trace, ent->model, ent->frameblend, ent->skeleton, vec3_origin, vec3_origin, 0, &ent->matrix, &ent->inversematrix, start, hitsupercontentsmask);
+ if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
+ *hitnetworkentity = cl.brushmodel_entities[i];
+ Collision_CombineTraces(&cliptrace, &trace, NULL, true);
+ }
+ }
+
+ // collide against player entities
+ if (hitnetworkplayers)
+ {
+ vec3_t origin, entmins, entmaxs;
+ matrix4x4_t entmatrix, entinversematrix;
+
+ if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
+ {
+ // don't hit network players, if we are a nonsolid player
+ if(cl.scores[cl.playerentity-1].frags == -666 || cl.scores[cl.playerentity-1].frags == -616)
+ goto skipnetworkplayers;
+ }
+
+ for (i = 1;i <= cl.maxclients;i++)
+ {
+ entity_render_t *ent = &cl.entities[i].render;
+
+ // don't hit ourselves
+ if (i == cl.playerentity)
+ continue;
+
+ // don't hit players that don't exist
+ if (!cl.scores[i-1].name[0])
+ continue;
+
+ if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
+ {
+ // don't hit spectators or nonsolid players
+ if(cl.scores[i-1].frags == -666 || cl.scores[i-1].frags == -616)
+ continue;
+ }
+
+ Matrix4x4_OriginFromMatrix(&ent->matrix, origin);
+ VectorAdd(origin, cl.playerstandmins, entmins);
+ VectorAdd(origin, cl.playerstandmaxs, entmaxs);
+ if (!BoxesOverlap(clipboxmins, clipboxmaxs, entmins, entmaxs))
+ continue;
+ Matrix4x4_CreateTranslate(&entmatrix, origin[0], origin[1], origin[2]);
+ Matrix4x4_CreateTranslate(&entinversematrix, -origin[0], -origin[1], -origin[2]);
+ Collision_ClipPointToGenericEntity(&trace, NULL, NULL, NULL, cl.playerstandmins, cl.playerstandmaxs, SUPERCONTENTS_BODY, &entmatrix, &entinversematrix, start, hitsupercontentsmask);
+ if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
+ *hitnetworkentity = i;
+ Collision_CombineTraces(&cliptrace, &trace, NULL, false);
+ }
+
+skipnetworkplayers:
+ ;
+ }
+
+ // clip to entities
+ // because this uses World_EntitiestoBox, we know all entity boxes overlap
+ // the clip region, so we can skip culling checks in the loop below
+ // note: if prog is NULL then there won't be any linked entities
+ numtouchedicts = 0;
+ if (hitcsqcentities && prog != NULL)
+ {
+ numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts);
+ if (numtouchedicts > MAX_EDICTS)
+ {
+ // this never happens
+ Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS);
+ numtouchedicts = MAX_EDICTS;
+ }
+ }
+ for (i = 0;i < numtouchedicts;i++)
+ {
+ touch = touchedicts[i];
+
+ if (PRVM_clientedictfloat(touch, solid) < SOLID_BBOX)
+ continue;
+ if (type == MOVE_NOMONSTERS && PRVM_clientedictfloat(touch, solid) != SOLID_BSP)
+ continue;
+
+ if (passedict)
+ {
+ // don't clip against self
+ if (passedict == touch)
+ continue;
+ // don't clip owned entities against owner
+ if (traceowner == touch)
+ continue;
+ // don't clip owner against owned entities
+ if (passedictprog == PRVM_clientedictedict(touch, owner))
+ continue;
+ // don't clip points against points (they can't collide)
+ if (VectorCompare(PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER)))
+ continue;
+ }
+
+ bodysupercontents = PRVM_clientedictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY;
+
+ // might interact, so do an exact clip
+ model = NULL;
+ if ((int) PRVM_clientedictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL)
+ model = CL_GetModelFromEdict(touch);
+ if (model)
+ Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2], PRVM_clientedictvector(touch, angles)[0], PRVM_clientedictvector(touch, angles)[1], PRVM_clientedictvector(touch, angles)[2], 1);
+ else
+ Matrix4x4_CreateTranslate(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2]);
+ Matrix4x4_Invert_Simple(&imatrix, &matrix);
+ VectorCopy(PRVM_clientedictvector(touch, mins), touchmins);
+ VectorCopy(PRVM_clientedictvector(touch, maxs), touchmaxs);
+ if ((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER)
+ Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipstart, hitsupercontentsmask);
+ else
+ Collision_ClipPointToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, hitsupercontentsmask);
+
+ if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
+ *hitnetworkentity = 0;
+ Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_clientedictfloat(touch, solid) == SOLID_BSP);
+ }
+
+finished:
+ return cliptrace;
+}
+
+/*
+==================
+CL_TraceLine
+==================
+*/
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+trace_t CL_TraceLine(const vec3_t start, const vec3_t pEnd, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities, qboolean hitsurfaces)
+#else
+trace_t CL_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities, qboolean hitsurfaces)
+#endif
+{
+ prvm_prog_t *prog = CLVM_prog;
+ int i, bodysupercontents;
+ int passedictprog;
+ prvm_edict_t *traceowner, *touch;
+ trace_t trace;
+ // temporary storage because prvm_vec_t may need conversion
+ vec3_t touchmins, touchmaxs;
+ // bounding box of entire move area
+ vec3_t clipboxmins, clipboxmaxs;
+ // size when clipping against monsters
+ vec3_t clipmins2, clipmaxs2;
+ // start and end origin of move
+ vec3_t clipstart, clipend;
+ // trace results
+ trace_t cliptrace;
+ // matrices to transform into/out of other entity's space
+ matrix4x4_t matrix, imatrix;
+ // model of other entity
+ dp_model_t *model;
+ // list of entities to test for collisions
+ int numtouchedicts;
+ static prvm_edict_t *touchedicts[MAX_EDICTS];
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+ vec3_t end;
+ vec_t len = 0;
+
+ if (VectorCompare(start, pEnd))
+ return CL_TracePoint(start, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities);
+
+ if(collision_endposnudge.value > 0)
+ {
+ // TRICK: make the trace 1 qu longer!
+ VectorSubtract(pEnd, start, end);
+ len = VectorNormalizeLength(end);
+ VectorMA(pEnd, collision_endposnudge.value, end, end);
+ }
+ else
+ VectorCopy(pEnd, end);
+#else
+ if (VectorCompare(start, end))
+ return CL_TracePoint(start, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities);
+#endif
+
+ if (hitnetworkentity)
+ *hitnetworkentity = 0;
+
+ VectorCopy(start, clipstart);
+ VectorCopy(end, clipend);
+ VectorClear(clipmins2);
+ VectorClear(clipmaxs2);
+#if COLLISIONPARANOID >= 3
+ Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]);
+#endif
+
+ // clip to world
+ Collision_ClipLineToWorld(&cliptrace, cl.worldmodel, clipstart, clipend, hitsupercontentsmask, hitsurfaces);
+ cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid;
+ if (cliptrace.startsolid || cliptrace.fraction < 1)
+ cliptrace.ent = prog ? prog->edicts : NULL;
+ if (type == MOVE_WORLDONLY)
+ goto finished;
+
+ if (type == MOVE_MISSILE)
+ {
+ // LordHavoc: modified this, was = -15, now -= 15
+ for (i = 0;i < 3;i++)
+ {
+ clipmins2[i] -= 15;
+ clipmaxs2[i] += 15;
+ }
+ }
+
+ // create the bounding box of the entire move
+ for (i = 0;i < 3;i++)
+ {
+ clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + clipmins2[i] - 1;
+ clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + clipmaxs2[i] + 1;
+ }
+
+ // debug override to test against everything
+ if (sv_debugmove.integer)
+ {
+ clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999;
+ clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999;
+ }
+
+ // if the passedict is world, make it NULL (to avoid two checks each time)
+ // this checks prog because this function is often called without a CSQC
+ // VM context
+ if (prog == NULL || passedict == prog->edicts)
+ passedict = NULL;
+ // precalculate prog value for passedict for comparisons
+ passedictprog = prog != NULL ? PRVM_EDICT_TO_PROG(passedict) : 0;
+ // precalculate passedict's owner edict pointer for comparisons
+ traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_clientedictedict(passedict, owner)) : NULL;
+
+ // collide against network entities
+ if (hitnetworkbrushmodels)
+ {
+ for (i = 0;i < cl.num_brushmodel_entities;i++)
+ {
+ entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render;
+ if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs))
+ continue;
+ Collision_ClipLineToGenericEntity(&trace, ent->model, ent->frameblend, ent->skeleton, vec3_origin, vec3_origin, 0, &ent->matrix, &ent->inversematrix, start, end, hitsupercontentsmask, hitsurfaces);
+ if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
+ *hitnetworkentity = cl.brushmodel_entities[i];
+ Collision_CombineTraces(&cliptrace, &trace, NULL, true);
+ }
+ }
+
+ // collide against player entities
+ if (hitnetworkplayers)
+ {
+ vec3_t origin, entmins, entmaxs;
+ matrix4x4_t entmatrix, entinversematrix;
+
+ if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
+ {
+ // don't hit network players, if we are a nonsolid player
+ if(cl.scores[cl.playerentity-1].frags == -666 || cl.scores[cl.playerentity-1].frags == -616)
+ goto skipnetworkplayers;
+ }
+
+ for (i = 1;i <= cl.maxclients;i++)
+ {
+ entity_render_t *ent = &cl.entities[i].render;
+
+ // don't hit ourselves
+ if (i == cl.playerentity)
+ continue;
+
+ // don't hit players that don't exist
+ if (!cl.scores[i-1].name[0])
+ continue;
+
+ if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
+ {
+ // don't hit spectators or nonsolid players
+ if(cl.scores[i-1].frags == -666 || cl.scores[i-1].frags == -616)
+ continue;
+ }
+
+ Matrix4x4_OriginFromMatrix(&ent->matrix, origin);
+ VectorAdd(origin, cl.playerstandmins, entmins);
+ VectorAdd(origin, cl.playerstandmaxs, entmaxs);
+ if (!BoxesOverlap(clipboxmins, clipboxmaxs, entmins, entmaxs))
+ continue;
+ Matrix4x4_CreateTranslate(&entmatrix, origin[0], origin[1], origin[2]);
+ Matrix4x4_CreateTranslate(&entinversematrix, -origin[0], -origin[1], -origin[2]);
+ Collision_ClipLineToGenericEntity(&trace, NULL, NULL, NULL, cl.playerstandmins, cl.playerstandmaxs, SUPERCONTENTS_BODY, &entmatrix, &entinversematrix, start, end, hitsupercontentsmask, hitsurfaces);
+ if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
+ *hitnetworkentity = i;
+ Collision_CombineTraces(&cliptrace, &trace, NULL, false);
+ }
+
+skipnetworkplayers:
+ ;
+ }
+
+ // clip to entities
+ // because this uses World_EntitiestoBox, we know all entity boxes overlap
+ // the clip region, so we can skip culling checks in the loop below
+ // note: if prog is NULL then there won't be any linked entities
+ numtouchedicts = 0;
+ if (hitcsqcentities && prog != NULL)
+ {
+ numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts);
+ if (numtouchedicts > MAX_EDICTS)
+ {
+ // this never happens
+ Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS);
+ numtouchedicts = MAX_EDICTS;
+ }
+ }
+ for (i = 0;i < numtouchedicts;i++)
+ {
+ touch = touchedicts[i];
+
+ if (PRVM_clientedictfloat(touch, solid) < SOLID_BBOX)
+ continue;
+ if (type == MOVE_NOMONSTERS && PRVM_clientedictfloat(touch, solid) != SOLID_BSP)
+ continue;
+
+ if (passedict)
+ {
+ // don't clip against self
+ if (passedict == touch)
+ continue;
+ // don't clip owned entities against owner
+ if (traceowner == touch)
+ continue;
+ // don't clip owner against owned entities
+ if (passedictprog == PRVM_clientedictedict(touch, owner))
+ continue;
+ // don't clip points against points (they can't collide)
+ if (VectorCompare(PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER)))
+ continue;
+ }
+
+ bodysupercontents = PRVM_clientedictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY;
+
+ // might interact, so do an exact clip
+ model = NULL;
+ if ((int) PRVM_clientedictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL)
+ model = CL_GetModelFromEdict(touch);
+ if (model)
+ Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2], PRVM_clientedictvector(touch, angles)[0], PRVM_clientedictvector(touch, angles)[1], PRVM_clientedictvector(touch, angles)[2], 1);
+ else
+ Matrix4x4_CreateTranslate(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2]);
+ Matrix4x4_Invert_Simple(&imatrix, &matrix);
+ VectorCopy(PRVM_clientedictvector(touch, mins), touchmins);
+ VectorCopy(PRVM_clientedictvector(touch, maxs), touchmaxs);
+ if (type == MOVE_MISSILE && (int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER)
+ Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask);
+ else
+ Collision_ClipLineToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipend, hitsupercontentsmask, hitsurfaces);
+
+ if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
+ *hitnetworkentity = 0;
+ Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_clientedictfloat(touch, solid) == SOLID_BSP);
+ }
+
+finished:
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+ if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0)
+ Collision_ShortenTrace(&cliptrace, len / (len + collision_endposnudge.value), pEnd);
+#endif
+ return cliptrace;
+}
+
+/*
+==================
+CL_Move
+==================
+*/
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+trace_t CL_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t pEnd, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities)
+#else
+trace_t CL_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities)
+#endif
+{
+ prvm_prog_t *prog = CLVM_prog;
+ vec3_t hullmins, hullmaxs;
+ int i, bodysupercontents;
+ int passedictprog;
+ qboolean pointtrace;
+ prvm_edict_t *traceowner, *touch;
+ trace_t trace;
+ // temporary storage because prvm_vec_t may need conversion
+ vec3_t touchmins, touchmaxs;
+ // bounding box of entire move area
+ vec3_t clipboxmins, clipboxmaxs;
+ // size of the moving object
+ vec3_t clipmins, clipmaxs;
+ // size when clipping against monsters
+ vec3_t clipmins2, clipmaxs2;
+ // start and end origin of move
+ vec3_t clipstart, clipend;
+ // trace results
+ trace_t cliptrace;
+ // matrices to transform into/out of other entity's space
+ matrix4x4_t matrix, imatrix;
+ // model of other entity
+ dp_model_t *model;
+ // list of entities to test for collisions
+ int numtouchedicts;
+ static prvm_edict_t *touchedicts[MAX_EDICTS];
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+ vec3_t end;
+ vec_t len = 0;
+
+ if (VectorCompare(mins, maxs))
+ {
+ vec3_t shiftstart, shiftend;
+ VectorAdd(start, mins, shiftstart);
+ VectorAdd(pEnd, mins, shiftend);
+ if (VectorCompare(start, pEnd))
+ trace = CL_TracePoint(shiftstart, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities);
+ else
+ trace = CL_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities, false);
+ VectorSubtract(trace.endpos, mins, trace.endpos);
+ return trace;
+ }
+
+ if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0)
+ {
+ // TRICK: make the trace 1 qu longer!
+ VectorSubtract(pEnd, start, end);
+ len = VectorNormalizeLength(end);
+ VectorMA(pEnd, collision_endposnudge.value, end, end);
+ }
+ else
+ VectorCopy(pEnd, end);
+#else
+ if (VectorCompare(mins, maxs))
+ {
+ vec3_t shiftstart, shiftend;
+ VectorAdd(start, mins, shiftstart);
+ VectorAdd(end, mins, shiftend);
+ if (VectorCompare(start, end))
+ trace = CL_TracePoint(shiftstart, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities);
+ else
+ trace = CL_TraceLine(shiftstart, shiftend, type, passedict, hitsupercontentsmask, hitnetworkbrushmodels, hitnetworkplayers, hitnetworkentity, hitcsqcentities, false);
+ VectorSubtract(trace.endpos, mins, trace.endpos);
+ return trace;
+ }
+#endif
+
+ if (hitnetworkentity)
+ *hitnetworkentity = 0;
+
+ VectorCopy(start, clipstart);
+ VectorCopy(end, clipend);
+ VectorCopy(mins, clipmins);
+ VectorCopy(maxs, clipmaxs);
+ VectorCopy(mins, clipmins2);
+ VectorCopy(maxs, clipmaxs2);
+#if COLLISIONPARANOID >= 3
+ Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]);
+#endif
+
+ // clip to world
+ Collision_ClipToWorld(&cliptrace, cl.worldmodel, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask);
+ cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid;
+ if (cliptrace.startsolid || cliptrace.fraction < 1)
+ cliptrace.ent = prog ? prog->edicts : NULL;
+ if (type == MOVE_WORLDONLY)
+ goto finished;
+
+ if (type == MOVE_MISSILE)
+ {
+ // LordHavoc: modified this, was = -15, now -= 15
+ for (i = 0;i < 3;i++)
+ {
+ clipmins2[i] -= 15;
+ clipmaxs2[i] += 15;
+ }
+ }
+
+ // get adjusted box for bmodel collisions if the world is q1bsp or hlbsp
+ if (cl.worldmodel && cl.worldmodel->brush.RoundUpToHullSize)
+ cl.worldmodel->brush.RoundUpToHullSize(cl.worldmodel, clipmins, clipmaxs, hullmins, hullmaxs);
+ else
+ {
+ VectorCopy(clipmins, hullmins);
+ VectorCopy(clipmaxs, hullmaxs);
+ }
+
+ // create the bounding box of the entire move
+ for (i = 0;i < 3;i++)
+ {
+ clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) + min(hullmins[i], clipmins2[i]) - 1;
+ clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + max(hullmaxs[i], clipmaxs2[i]) + 1;
+ }
+
+ // debug override to test against everything
+ if (sv_debugmove.integer)
+ {
+ clipboxmins[0] = clipboxmins[1] = clipboxmins[2] = -999999999;
+ clipboxmaxs[0] = clipboxmaxs[1] = clipboxmaxs[2] = 999999999;
+ }
+
+ // if the passedict is world, make it NULL (to avoid two checks each time)
+ // this checks prog because this function is often called without a CSQC
+ // VM context
+ if (prog == NULL || passedict == prog->edicts)
+ passedict = NULL;
+ // precalculate prog value for passedict for comparisons
+ passedictprog = prog != NULL ? PRVM_EDICT_TO_PROG(passedict) : 0;
+ // figure out whether this is a point trace for comparisons
+ pointtrace = VectorCompare(clipmins, clipmaxs);
+ // precalculate passedict's owner edict pointer for comparisons
+ traceowner = passedict ? PRVM_PROG_TO_EDICT(PRVM_clientedictedict(passedict, owner)) : NULL;
+
+ // collide against network entities
+ if (hitnetworkbrushmodels)
+ {
+ for (i = 0;i < cl.num_brushmodel_entities;i++)
+ {
+ entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render;
+ if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs))
+ continue;
+ Collision_ClipToGenericEntity(&trace, ent->model, ent->frameblend, ent->skeleton, vec3_origin, vec3_origin, 0, &ent->matrix, &ent->inversematrix, start, mins, maxs, end, hitsupercontentsmask);
+ if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
+ *hitnetworkentity = cl.brushmodel_entities[i];
+ Collision_CombineTraces(&cliptrace, &trace, NULL, true);
+ }
+ }
+
+ // collide against player entities
+ if (hitnetworkplayers)
+ {
+ vec3_t origin, entmins, entmaxs;
+ matrix4x4_t entmatrix, entinversematrix;
+
+ if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
+ {
+ // don't hit network players, if we are a nonsolid player
+ if(cl.scores[cl.playerentity-1].frags == -666 || cl.scores[cl.playerentity-1].frags == -616)
+ goto skipnetworkplayers;
+ }
+
+ for (i = 1;i <= cl.maxclients;i++)
+ {
+ entity_render_t *ent = &cl.entities[i].render;
+
+ // don't hit ourselves
+ if (i == cl.playerentity)
+ continue;
+
+ // don't hit players that don't exist
+ if (!cl.scores[i-1].name[0])
+ continue;
+
+ if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
+ {
+ // don't hit spectators or nonsolid players
+ if(cl.scores[i-1].frags == -666 || cl.scores[i-1].frags == -616)
+ continue;
+ }
+
+ Matrix4x4_OriginFromMatrix(&ent->matrix, origin);
+ VectorAdd(origin, cl.playerstandmins, entmins);
+ VectorAdd(origin, cl.playerstandmaxs, entmaxs);
+ if (!BoxesOverlap(clipboxmins, clipboxmaxs, entmins, entmaxs))
+ continue;
+ Matrix4x4_CreateTranslate(&entmatrix, origin[0], origin[1], origin[2]);
+ Matrix4x4_CreateTranslate(&entinversematrix, -origin[0], -origin[1], -origin[2]);
+ Collision_ClipToGenericEntity(&trace, NULL, NULL, NULL, cl.playerstandmins, cl.playerstandmaxs, SUPERCONTENTS_BODY, &entmatrix, &entinversematrix, start, mins, maxs, end, hitsupercontentsmask);
+ if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
+ *hitnetworkentity = i;
+ Collision_CombineTraces(&cliptrace, &trace, NULL, false);
+ }
+
+skipnetworkplayers:
+ ;
+ }
+
+ // clip to entities
+ // because this uses World_EntitiestoBox, we know all entity boxes overlap
+ // the clip region, so we can skip culling checks in the loop below
+ // note: if prog is NULL then there won't be any linked entities
+ numtouchedicts = 0;
+ if (hitcsqcentities && prog != NULL)
+ {
+ numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts);
+ if (numtouchedicts > MAX_EDICTS)
+ {
+ // this never happens
+ Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS);
+ numtouchedicts = MAX_EDICTS;
+ }
+ }
+ for (i = 0;i < numtouchedicts;i++)
+ {
+ touch = touchedicts[i];
+
+ if (PRVM_clientedictfloat(touch, solid) < SOLID_BBOX)
+ continue;
+ if (type == MOVE_NOMONSTERS && PRVM_clientedictfloat(touch, solid) != SOLID_BSP)
+ continue;
+
+ if (passedict)
+ {
+ // don't clip against self
+ if (passedict == touch)
+ continue;
+ // don't clip owned entities against owner
+ if (traceowner == touch)
+ continue;
+ // don't clip owner against owned entities
+ if (passedictprog == PRVM_clientedictedict(touch, owner))
+ continue;
+ // don't clip points against points (they can't collide)
+ if (pointtrace && VectorCompare(PRVM_clientedictvector(touch, mins), PRVM_clientedictvector(touch, maxs)) && (type != MOVE_MISSILE || !((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER)))
+ continue;
+ }
+
+ bodysupercontents = PRVM_clientedictfloat(touch, solid) == SOLID_CORPSE ? SUPERCONTENTS_CORPSE : SUPERCONTENTS_BODY;
+
+ // might interact, so do an exact clip
+ model = NULL;
+ if ((int) PRVM_clientedictfloat(touch, solid) == SOLID_BSP || type == MOVE_HITMODEL)
+ model = CL_GetModelFromEdict(touch);
+ if (model)
+ Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2], PRVM_clientedictvector(touch, angles)[0], PRVM_clientedictvector(touch, angles)[1], PRVM_clientedictvector(touch, angles)[2], 1);
+ else
+ Matrix4x4_CreateTranslate(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2]);
+ Matrix4x4_Invert_Simple(&imatrix, &matrix);
+ VectorCopy(PRVM_clientedictvector(touch, mins), touchmins);
+ VectorCopy(PRVM_clientedictvector(touch, maxs), touchmaxs);
+ if ((int)PRVM_clientedictfloat(touch, flags) & FL_MONSTER)
+ Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins2, clipmaxs2, clipend, hitsupercontentsmask);
+ else
+ Collision_ClipToGenericEntity(&trace, model, touch->priv.server->frameblend, &touch->priv.server->skeleton, touchmins, touchmaxs, bodysupercontents, &matrix, &imatrix, clipstart, clipmins, clipmaxs, clipend, hitsupercontentsmask);
+
+ if (cliptrace.realfraction > trace.realfraction && hitnetworkentity)
+ *hitnetworkentity = 0;
+ Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_clientedictfloat(touch, solid) == SOLID_BSP);
+ }
+
+finished:
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+ if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0)
+ Collision_ShortenTrace(&cliptrace, len / (len + collision_endposnudge.value), pEnd);
+#endif
+ return cliptrace;
+}
+
+/*
+==================
+CL_Cache_TraceLine
+==================
+*/
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+trace_t CL_Cache_TraceLineSurfaces(const vec3_t start, const vec3_t pEnd, int type, int hitsupercontentsmask)
+#else
+trace_t CL_Cache_TraceLineSurfaces(const vec3_t start, const vec3_t end, int type, int hitsupercontentsmask)
+#endif
+{
+ prvm_prog_t *prog = CLVM_prog;
+ int i;
+ prvm_edict_t *touch;
+ trace_t trace;
+ // bounding box of entire move area
+ vec3_t clipboxmins, clipboxmaxs;
+ // start and end origin of move
+ vec3_t clipstart, clipend;
+ // trace results
+ trace_t cliptrace;
+ // matrices to transform into/out of other entity's space
+ matrix4x4_t matrix, imatrix;
+ // model of other entity
+ dp_model_t *model;
+ // list of entities to test for collisions
+ int numtouchedicts;
+ static prvm_edict_t *touchedicts[MAX_EDICTS];
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+ vec3_t end;
+ vec_t len = 0;
+
+ if(collision_endposnudge.value > 0 && !VectorCompare(start, pEnd))
+ {
+ // TRICK: make the trace 1 qu longer!
+ VectorSubtract(pEnd, start, end);
+ len = VectorNormalizeLength(end);
+ VectorMA(pEnd, collision_endposnudge.value, end, end);
+ }
+ else
+ VectorCopy(pEnd, end);
+#endif
+
+ VectorCopy(start, clipstart);
+ VectorCopy(end, clipend);
+#if COLLISIONPARANOID >= 3
+ Con_Printf("move(%f %f %f,%f %f %f)", clipstart[0], clipstart[1], clipstart[2], clipend[0], clipend[1], clipend[2]);
+#endif
+
+ // clip to world
+ Collision_Cache_ClipLineToWorldSurfaces(&cliptrace, cl.worldmodel, clipstart, clipend, hitsupercontentsmask);
+ cliptrace.worldstartsolid = cliptrace.bmodelstartsolid = cliptrace.startsolid;
+ if (cliptrace.startsolid || cliptrace.fraction < 1)
+ cliptrace.ent = prog ? prog->edicts : NULL;
+ if (type == MOVE_WORLDONLY)
+ goto finished;
+
+ // create the bounding box of the entire move
+ for (i = 0;i < 3;i++)
+ {
+ clipboxmins[i] = min(clipstart[i], cliptrace.endpos[i]) - 1;
+ clipboxmaxs[i] = max(clipstart[i], cliptrace.endpos[i]) + 1;
+ }
+
+ // if the passedict is world, make it NULL (to avoid two checks each time)
+ // this checks prog because this function is often called without a CSQC
+ // VM context
+
+ // collide against network entities
+ for (i = 0;i < cl.num_brushmodel_entities;i++)
+ {
+ entity_render_t *ent = &cl.entities[cl.brushmodel_entities[i]].render;
+ if (!BoxesOverlap(clipboxmins, clipboxmaxs, ent->mins, ent->maxs))
+ continue;
+ Collision_Cache_ClipLineToGenericEntitySurfaces(&trace, ent->model, &ent->matrix, &ent->inversematrix, start, end, hitsupercontentsmask);
+ Collision_CombineTraces(&cliptrace, &trace, NULL, true);
+ }
+
+ // clip to entities
+ // because this uses World_EntitiestoBox, we know all entity boxes overlap
+ // the clip region, so we can skip culling checks in the loop below
+ // note: if prog is NULL then there won't be any linked entities
+ numtouchedicts = 0;
+ if (prog != NULL)
+ {
+ numtouchedicts = World_EntitiesInBox(&cl.world, clipboxmins, clipboxmaxs, MAX_EDICTS, touchedicts);
+ if (numtouchedicts > MAX_EDICTS)
+ {
+ // this never happens
+ Con_Printf("CL_EntitiesInBox returned %i edicts, max was %i\n", numtouchedicts, MAX_EDICTS);
+ numtouchedicts = MAX_EDICTS;
+ }
+ }
+ for (i = 0;i < numtouchedicts;i++)
+ {
+ touch = touchedicts[i];
+ // might interact, so do an exact clip
+ // only hit entity models, not collision shapes
+ model = CL_GetModelFromEdict(touch);
+ if (!model)
+ continue;
+ // animated models are too slow to collide against and can't be cached
+ if (touch->priv.server->frameblend || touch->priv.server->skeleton.relativetransforms)
+ continue;
+ if (type == MOVE_NOMONSTERS && PRVM_clientedictfloat(touch, solid) != SOLID_BSP)
+ continue;
+ Matrix4x4_CreateFromQuakeEntity(&matrix, PRVM_clientedictvector(touch, origin)[0], PRVM_clientedictvector(touch, origin)[1], PRVM_clientedictvector(touch, origin)[2], PRVM_clientedictvector(touch, angles)[0], PRVM_clientedictvector(touch, angles)[1], PRVM_clientedictvector(touch, angles)[2], 1);
+ Matrix4x4_Invert_Simple(&imatrix, &matrix);
+ Collision_Cache_ClipLineToGenericEntitySurfaces(&trace, model, &matrix, &imatrix, clipstart, clipend, hitsupercontentsmask);
+ Collision_CombineTraces(&cliptrace, &trace, (void *)touch, PRVM_clientedictfloat(touch, solid) == SOLID_BSP);
+ }
+
+finished:
+#ifdef COLLISION_STUPID_TRACE_ENDPOS_IN_SOLID_WORKAROUND
+ if(!VectorCompare(start, pEnd) && collision_endposnudge.value > 0)
+ Collision_ShortenTrace(&cliptrace, len / (len + collision_endposnudge.value), pEnd);
+#endif
+ return cliptrace;
+}
+
diff --git a/app/jni/cl_collision.h b/app/jni/cl_collision.h
new file mode 100644
index 0000000..851502c
--- /dev/null
+++ b/app/jni/cl_collision.h
@@ -0,0 +1,19 @@
+
+#ifndef CL_COLLISION_H
+#define CL_COLLISION_H
+
+float CL_SelectTraceLine(const vec3_t start, const vec3_t end, vec3_t impact, vec3_t normal, int *hitent, entity_render_t *ignoreent);
+void CL_FindNonSolidLocation(const vec3_t in, vec3_t out, vec_t radius);
+
+dp_model_t *CL_GetModelByIndex(int modelindex);
+dp_model_t *CL_GetModelFromEdict(prvm_edict_t *ed);
+
+void CL_LinkEdict(prvm_edict_t *ent);
+int CL_GenericHitSuperContentsMask(const prvm_edict_t *edict);
+trace_t CL_TraceBox(const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities);
+trace_t CL_TraceLine(const vec3_t start, const vec3_t end, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities, qboolean hitsurfaces);
+trace_t CL_TracePoint(const vec3_t start, int type, prvm_edict_t *passedict, int hitsupercontentsmask, qboolean hitnetworkbrushmodels, qboolean hitnetworkplayers, int *hitnetworkentity, qboolean hitcsqcentities);
+trace_t CL_Cache_TraceLineSurfaces(const vec3_t start, const vec3_t end, int type, int hitsupercontentsmask);
+#define CL_PointSuperContents(point) (CL_TracePoint((point), sv_gameplayfix_swiminbmodels.integer ? MOVE_NOMONSTERS : MOVE_WORLDONLY, NULL, 0, true, false, NULL, false).startsupercontents)
+
+#endif
diff --git a/app/jni/cl_demo.c b/app/jni/cl_demo.c
new file mode 100644
index 0000000..f30eff8
--- /dev/null
+++ b/app/jni/cl_demo.c
@@ -0,0 +1,617 @@
+/*
+Copyright (C) 1996-1997 Id Software, Inc.
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+#include "quakedef.h"
+
+extern cvar_t cl_capturevideo;
+extern cvar_t cl_capturevideo_demo_stop;
+int old_vsync = 0;
+
+static void CL_FinishTimeDemo (void);
+
+/*
+==============================================================================
+
+DEMO CODE
+
+When a demo is playing back, all outgoing network messages are skipped, and
+incoming messages are read from the demo file.
+
+Whenever cl.time gets past the last received message, another message is
+read from the demo file.
+==============================================================================
+*/
+
+/*
+=====================
+CL_NextDemo
+
+Called to play the next demo in the demo loop
+=====================
+*/
+void CL_NextDemo (void)
+{
+ char str[MAX_INPUTLINE];
+
+ if (cls.demonum == -1)
+ return; // don't play demos
+
+ if (!cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS)
+ {
+ cls.demonum = 0;
+ if (!cls.demos[cls.demonum][0])
+ {
+ Con_Print("No demos listed with startdemos\n");
+ cls.demonum = -1;
+ return;
+ }
+ }
+
+ dpsnprintf (str, sizeof(str), "playdemo %s\n", cls.demos[cls.demonum]);
+ Cbuf_InsertText (str);
+ cls.demonum++;
+}
+
+/*
+==============
+CL_StopPlayback
+
+Called when a demo file runs out, or the user starts a game
+==============
+*/
+// LordHavoc: now called only by CL_Disconnect
+void CL_StopPlayback (void)
+{
+ if (cl_capturevideo_demo_stop.integer)
+ Cvar_Set("cl_capturevideo", "0");
+
+ if (!cls.demoplayback)
+ return;
+
+ FS_Close (cls.demofile);
+ cls.demoplayback = false;
+ cls.demofile = NULL;
+
+ if (cls.timedemo)
+ CL_FinishTimeDemo ();
+
+ if (!cls.demostarting) // only quit if not starting another demo
+ if (COM_CheckParm("-demo") || COM_CheckParm("-capturedemo"))
+ Host_Quit_f();
+
+}
+
+/*
+====================
+CL_WriteDemoMessage
+
+Dumps the current net message, prefixed by the length and view angles
+#====================
+*/
+void CL_WriteDemoMessage (sizebuf_t *message)
+{
+ int len;
+ int i;
+ float f;
+
+ if (cls.demopaused) // LordHavoc: pausedemo
+ return;
+
+ len = LittleLong (message->cursize);
+ FS_Write (cls.demofile, &len, 4);
+ for (i=0 ; i<3 ; i++)
+ {
+ f = LittleFloat (cl.viewangles[i]);
+ FS_Write (cls.demofile, &f, 4);
+ }
+ FS_Write (cls.demofile, message->data, message->cursize);
+}
+
+/*
+====================
+CL_CutDemo
+
+Dumps the current demo to a buffer, and resets the demo to its starting point.
+Used to insert csprogs.dat files as a download to the beginning of a demo file.
+====================
+*/
+void CL_CutDemo (unsigned char **buf, fs_offset_t *filesize)
+{
+ *buf = NULL;
+ *filesize = 0;
+
+ FS_Close(cls.demofile);
+ *buf = FS_LoadFile(cls.demoname, tempmempool, false, filesize);
+
+ // restart the demo recording
+ cls.demofile = FS_OpenRealFile(cls.demoname, "wb", false);
+ if(!cls.demofile)
+ Sys_Error("failed to reopen the demo file");
+ FS_Printf(cls.demofile, "%i\n", cls.forcetrack);
+}
+
+/*
+====================
+CL_PasteDemo
+
+Adds the cut stuff back to the demo. Also frees the buffer.
+Used to insert csprogs.dat files as a download to the beginning of a demo file.
+====================
+*/
+void CL_PasteDemo (unsigned char **buf, fs_offset_t *filesize)
+{
+ fs_offset_t startoffset = 0;
+
+ if(!*buf)
+ return;
+
+ // skip cdtrack
+ while(startoffset < *filesize && ((char *)(*buf))[startoffset] != '\n')
+ ++startoffset;
+ if(startoffset < *filesize)
+ ++startoffset;
+
+ FS_Write(cls.demofile, *buf + startoffset, *filesize - startoffset);
+
+ Mem_Free(*buf);
+ *buf = NULL;
+ *filesize = 0;
+}
+
+/*
+====================
+CL_ReadDemoMessage
+
+Handles playback of demos
+====================
+*/
+void CL_ReadDemoMessage(void)
+{
+ int i;
+ float f;
+
+ if (!cls.demoplayback)
+ return;
+
+ // LordHavoc: pausedemo
+ if (cls.demopaused)
+ return;
+
+ for (;;)
+ {
+ // decide if it is time to grab the next message
+ // always grab until fully connected
+ if (cls.signon == SIGNONS)
+ {
+ if (cls.timedemo)
+ {
+ cls.td_frames++;
+ cls.td_onesecondframes++;
+ // if this is the first official frame we can now grab the real
+ // td_starttime so the bogus time on the first frame doesn't
+ // count against the final report
+ if (cls.td_frames == 0)
+ {
+ cls.td_starttime = realtime;
+ cls.td_onesecondnexttime = cl.time + 1;
+ cls.td_onesecondrealtime = realtime;
+ cls.td_onesecondframes = 0;
+ cls.td_onesecondminfps = 0;
+ cls.td_onesecondmaxfps = 0;
+ cls.td_onesecondavgfps = 0;
+ cls.td_onesecondavgcount = 0;
+ }
+ if (cl.time >= cls.td_onesecondnexttime)
+ {
+ double fps = cls.td_onesecondframes / (realtime - cls.td_onesecondrealtime);
+ if (cls.td_onesecondavgcount == 0)
+ {
+ cls.td_onesecondminfps = fps;
+ cls.td_onesecondmaxfps = fps;
+ }
+ cls.td_onesecondrealtime = realtime;
+ cls.td_onesecondminfps = min(cls.td_onesecondminfps, fps);
+ cls.td_onesecondmaxfps = max(cls.td_onesecondmaxfps, fps);
+ cls.td_onesecondavgfps += fps;
+ cls.td_onesecondavgcount++;
+ cls.td_onesecondframes = 0;
+ cls.td_onesecondnexttime++;
+ }
+ }
+ else if (cl.time <= cl.mtime[0])
+ {
+ // don't need another message yet
+ return;
+ }
+ }
+
+ // get the next message
+ FS_Read(cls.demofile, &cl_message.cursize, 4);
+ cl_message.cursize = LittleLong(cl_message.cursize);
+ if(cl_message.cursize & DEMOMSG_CLIENT_TO_SERVER) // This is a client->server message! Ignore for now!
+ {
+ // skip over demo packet
+ FS_Seek(cls.demofile, 12 + (cl_message.cursize & (~DEMOMSG_CLIENT_TO_SERVER)), SEEK_CUR);
+ continue;
+ }
+ if (cl_message.cursize > cl_message.maxsize)
+ {
+ Con_Printf("Demo message (%i) > cl_message.maxsize (%i)", cl_message.cursize, cl_message.maxsize);
+ cl_message.cursize = 0;
+ CL_Disconnect();
+ return;
+ }
+ VectorCopy(cl.mviewangles[0], cl.mviewangles[1]);
+ for (i = 0;i < 3;i++)
+ {
+ FS_Read(cls.demofile, &f, 4);
+ cl.mviewangles[0][i] = LittleFloat(f);
+ }
+
+ if (FS_Read(cls.demofile, cl_message.data, cl_message.cursize) == cl_message.cursize)
+ {
+ MSG_BeginReading(&cl_message);
+ CL_ParseServerMessage();
+
+ if (cls.signon != SIGNONS)
+ Cbuf_Execute(); // immediately execute svc_stufftext if in the demo before connect!
+
+ // In case the demo contains a "svc_disconnect" message
+ if (!cls.demoplayback)
+ return;
+
+ if (cls.timedemo)
+ return;
+ }
+ else
+ {
+ CL_Disconnect();
+ return;
+ }
+ }
+}
+
+
+/*
+====================
+CL_Stop_f
+
+stop recording a demo
+====================
+*/
+void CL_Stop_f (void)
+{
+ sizebuf_t buf;
+ unsigned char bufdata[64];
+
+ if (!cls.demorecording)
+ {
+ Con_Print("Not recording a demo.\n");
+ return;
+ }
+
+// write a disconnect message to the demo file
+ // LordHavoc: don't replace the cl_message when doing this
+ buf.data = bufdata;
+ buf.maxsize = sizeof(bufdata);
+ SZ_Clear(&buf);
+ MSG_WriteByte(&buf, svc_disconnect);
+ CL_WriteDemoMessage(&buf);
+
+// finish up
+ if(cl_autodemo.integer && (cl_autodemo_delete.integer & 1))
+ {
+ FS_RemoveOnClose(cls.demofile);
+ Con_Print("Completed and deleted demo\n");
+ }
+ else
+ Con_Print("Completed demo\n");
+ FS_Close (cls.demofile);
+ cls.demofile = NULL;
+ cls.demorecording = false;
+}
+
+/*
+====================
+CL_Record_f
+
+record